1.获取临时code
/*** 获取临时code*/@GetMapping("/getCode")public ActionResult<String> getCode() {String randomCode = RandomUtil.randomNumbers(6);// 300秒redisUtil.insert(VALIDCODE + randomCode, randomCode, 300);return ActionResult.success("success",randomCode);}
2.获取token
2.1 请求参数
import lombok.Data;@Data
public class StaticTokenDTO {// 临时codeprivate Integer code;// 自定义的appIDprivate String appId;// 自定义的秘钥private String appSecret;}
2.2配置的应用凭证
// 配置的应用凭证 实际项目中应该从数据库或配置文件中获取private static final Map<String, String> APP_CREDENTIALS = new HashMap<>();static {APP_CREDENTIALS.put("remote001","MIGfMA0GCSqGSIb3");}
2.3 获取静态token
private static final String VALIDCODE = "validcode:";private static final String ACCESS_TOKEN_PREFIX = "access_token:";private static final long ACCESS_TOKEN_EXPIRE = 7200; // 2小时过期private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";private static final long REFRESH_TOKEN_EXPIRE = 604800; // 7天过期/*** 获取静态token*/@PostMapping("/getStaticToken")public ActionResult<Map<String, Object>> getStaticToken(StaticTokenDTO staticToken) {try {// 1. 参数验证if (staticToken == null) {return ActionResult.fail("参数不能为空");}if (ObjectUtil.isEmpty(staticToken.getCode()) || ObjectUtil.isEmpty(redisUtil.getString(VALIDCODE + staticToken.getCode()))) {return ActionResult.fail("code 验证失败");}if(StringUtils.isBlank(staticToken.getAppId()) || StringUtils.isBlank(staticToken.getAppSecret())){return ActionResult.fail("appId 和 appSecret 不能为空");}// 2. 验证应用凭证String expectedSecret = APP_CREDENTIALS.get(staticToken.getAppId());if (expectedSecret == null) {return ActionResult.fail("无效的 appId");}if (!expectedSecret.equals(staticToken.getAppSecret())) {return ActionResult.fail("appSecret 验证失败");}// 删除 验证码redisUtil.remove(VALIDCODE + staticToken.getCode());// 3. 生成安全的 accessTokenString accessToken = generateSecureAccessToken(staticToken);// 4. 将 token 存储到 Redis 中,设置过期时间String tokenKey = ACCESS_TOKEN_PREFIX + accessToken;Map<String, Object> tokenInfo = new HashMap<>();tokenInfo.put("appId", staticToken.getAppId());tokenInfo.put("createTime", System.currentTimeMillis());tokenInfo.put("expireTime", System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE * 1000);redisUtil.insert(tokenKey, JsonUtil.getObjectToString(tokenInfo), ACCESS_TOKEN_EXPIRE);// 4.1 生成并存储 refreshToken(与 accessToken 绑定)String refreshToken = RandomUtil.randomString(32);String refreshTokenKey = REFRESH_TOKEN_PREFIX + refreshToken;long refreshExpire = REFRESH_TOKEN_EXPIRE;Map<String, Object> refreshInfo = new HashMap<>();refreshInfo.put("appId", staticToken.getAppId());refreshInfo.put("bindAccessToken", accessToken);refreshInfo.put("createTime", System.currentTimeMillis());refreshInfo.put("expireTime", System.currentTimeMillis() + refreshExpire * 1000);redisUtil.insert(refreshTokenKey, JsonUtil.getObjectToString(refreshInfo), refreshExpire);// 5. 返回结果Map<String, Object> result = new HashMap<>();result.put("accessToken", accessToken);result.put("refreshToken", refreshToken);result.put("expiresIn", ACCESS_TOKEN_EXPIRE);result.put("tokenType", "Bearer");return ActionResult.success(result);} catch (Exception e) {return ActionResult.fail("生成 token 失败: " + e.getMessage());}}
3.刷新 token 调用刷新token方法后,以前的accessToken 被失效
@PostMapping("/refreshToken")public ActionResult<Map<String, Object>> refreshToken(String refreshToken) {try {// 校验 refreshToken 是否有效if (StringUtils.isBlank(refreshToken)) {return ActionResult.fail("无效的 refreshToken");}String refreshKey = REFRESH_TOKEN_PREFIX + refreshToken;String refreshInfoStr = (String) redisUtil.getString(refreshKey);if (StringUtils.isBlank(refreshInfoStr)) {return ActionResult.fail("无效的 refreshToken");}Map<String, Object> refreshInfo = JsonUtil.stringToMap(refreshInfoStr);String appId = String.valueOf(refreshInfo.get("appId"));String bindAccessToken = String.valueOf(refreshInfo.get("bindAccessToken"));// 尝试解析旧 accessToken 获取 codeInteger code = null;try {String jwtSecret = getJwtSecret();Map<String, Object> claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(bindAccessToken).getBody();Object codeObj = claims.get("code");if (codeObj instanceof Integer) {code = (Integer) codeObj;} else if (codeObj instanceof Number) {code = ((Number) codeObj).intValue();}} catch (Exception ignore) {}// 失效旧的 accessToken 和 refreshTokenif (StringUtils.isNotBlank(bindAccessToken)) {redisUtil.remove(ACCESS_TOKEN_PREFIX + bindAccessToken);}redisUtil.remove(refreshKey);// 生成新的 accessTokenStaticTokenDTO staticTokenDTO = new StaticTokenDTO();staticTokenDTO.setAppId(appId);staticTokenDTO.setAppSecret(APP_CREDENTIALS.get(appId));staticTokenDTO.setCode(code);String newAccessToken = generateSecureAccessToken(staticTokenDTO);// 存储新的 accessTokenString newAccessTokenKey = ACCESS_TOKEN_PREFIX + newAccessToken;Map<String, Object> tokenInfo = new HashMap<>();tokenInfo.put("appId", appId);tokenInfo.put("createTime", System.currentTimeMillis());tokenInfo.put("expireTime", System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE * 1000);redisUtil.insert(newAccessTokenKey, JsonUtil.getObjectToString(tokenInfo), ACCESS_TOKEN_EXPIRE);// 生成并存储新的 refreshToken(与新的 accessToken 绑定)String newRefreshToken = RandomUtil.randomString(32);String newRefreshTokenKey = REFRESH_TOKEN_PREFIX + newRefreshToken;long newRefreshExpire = REFRESH_TOKEN_EXPIRE;Map<String, Object> newRefreshInfo = new HashMap<>();newRefreshInfo.put("appId", appId);newRefreshInfo.put("bindAccessToken", newAccessToken);newRefreshInfo.put("createTime", System.currentTimeMillis());newRefreshInfo.put("expireTime", System.currentTimeMillis() + newRefreshExpire * 1000);redisUtil.insert(newRefreshTokenKey, JsonUtil.getObjectToString(newRefreshInfo), newRefreshExpire);Map<String, Object> result = new HashMap<>();result.put("accessToken", newAccessToken);result.put("refreshToken", newRefreshToken);result.put("expiresIn", ACCESS_TOKEN_EXPIRE);result.put("tokenType", "Bearer");return ActionResult.success(result);} catch (Exception e) {return ActionResult.fail("刷新 token 失败: " + e.getMessage());}}
4.验证token
/*** 验证访问令牌*/private boolean validateAccessToken(String accessToken) {try {if (StringUtils.isBlank(accessToken)) {return true;}// 1. 检查 Redis 中是否存在该 tokenString tokenKey = ACCESS_TOKEN_PREFIX + accessToken;String tokenInfoStr = (String) redisUtil.getString(tokenKey);if (StringUtils.isBlank(tokenInfoStr)) {return true;}// 2. 验证 JWT 签名和过期时间String jwtSecret = getJwtSecret();try {Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(accessToken);return false;} catch (Exception e) {// JWT 验证失败,删除 Redis 中的记录redisUtil.remove(tokenKey);return true;}} catch (Exception e) {return true;}}
5.生成安全的访问令牌方法
/*** 生成安全的访问令牌*/private String generateSecureAccessToken(StaticTokenDTO staticToken) {long currentTime = System.currentTimeMillis();long expireTime = currentTime + ACCESS_TOKEN_EXPIRE * 1000;// 生成随机数防止重放攻击String nonce = RandomUtil.randomString(16);// 创建 JWT payloadMap<String, Object> claims = new HashMap<>();claims.put("appId", staticToken.getAppId());claims.put("nonce", nonce);claims.put("timestamp", currentTime);claims.put("iat", currentTime / 1000);claims.put("exp", expireTime / 1000);claims.put("code", staticToken.getCode());// 使用 HMAC-SHA256 签名生成 JWTString jwtSecret = getJwtSecret();String jwt = Jwts.builder().setClaims(claims).setIssuedAt(new Date(currentTime)).setExpiration(new Date(expireTime)).signWith(SignatureAlgorithm.HS256, jwtSecret).compact();return jwt;}
6.获取 JWT 密钥 (这个可以随便写)
/*** 获取 JWT 密钥*/private String getJwtSecret() {return AuthConsts.JWT_SECRET;}
7.测试
@GetMapping("/getEnterpriseGradePage")public ActionResult<IPage<EnterpriseGradeVO>> getEnterpriseGradePage(HttpServletRequest request, Pagination page) {String authorization = request.getHeader("Authorization");if (validateAccessToken(authorization)) {return ActionResult.fail("无效的 accessToken");}return ActionResult.success(remoteService.getEnterpriseGradePage(page));}
8.主要依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.7.0</version></dependency>