1.获取临时code
bash
复制代码
/**
* 获取临时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 请求参数
bash
复制代码
import lombok.Data;
@Data
public class StaticTokenDTO {
// 临时code
private Integer code;
// 自定义的appID
private String appId;
// 自定义的秘钥
private String appSecret;
}
2.2配置的应用凭证
bash
复制代码
// 配置的应用凭证 实际项目中应该从数据库或配置文件中获取
private static final Map<String, String> APP_CREDENTIALS = new HashMap<>();
static {
APP_CREDENTIALS.put("remote001","MIGfMA0GCSqGSIb3");
}
2.3 获取静态token
bash
复制代码
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. 生成安全的 accessToken
String 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 被失效
bash
复制代码
@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 获取 code
Integer 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 和 refreshToken
if (StringUtils.isNotBlank(bindAccessToken)) {
redisUtil.remove(ACCESS_TOKEN_PREFIX + bindAccessToken);
}
redisUtil.remove(refreshKey);
// 生成新的 accessToken
StaticTokenDTO staticTokenDTO = new StaticTokenDTO();
staticTokenDTO.setAppId(appId);
staticTokenDTO.setAppSecret(APP_CREDENTIALS.get(appId));
staticTokenDTO.setCode(code);
String newAccessToken = generateSecureAccessToken(staticTokenDTO);
// 存储新的 accessToken
String 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
bash
复制代码
/**
* 验证访问令牌
*/
private boolean validateAccessToken(String accessToken) {
try {
if (StringUtils.isBlank(accessToken)) {
return true;
}
// 1. 检查 Redis 中是否存在该 token
String 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.生成安全的访问令牌方法
bash
复制代码
/**
* 生成安全的访问令牌
*/
private String generateSecureAccessToken(StaticTokenDTO staticToken) {
long currentTime = System.currentTimeMillis();
long expireTime = currentTime + ACCESS_TOKEN_EXPIRE * 1000;
// 生成随机数防止重放攻击
String nonce = RandomUtil.randomString(16);
// 创建 JWT payload
Map<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 签名生成 JWT
String 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 密钥 (这个可以随便写)
bash
复制代码
/**
* 获取 JWT 密钥
*/
private String getJwtSecret() {
return AuthConsts.JWT_SECRET;
}
7.测试
bash
复制代码
@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.主要依赖
bash
复制代码
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>