spring security使用双Token实现无感刷新
·
spring security使用双Token实现无感刷新
一、整体实现流程

在实际业务中,用户频繁登录会带来不便,因此通常采用 双 Token 机制 来提升用户体验:
- Access Token(短期,有效期较短)用于接口访问;
- Refresh Token(长期,通常一天)仅用于刷新 Access Token。
整体流程:
用户登录成功后,服务端生成 Access Token 与 Refresh Token 并返回给前端存储在localStorage。
前端每次调用接口时携带 Access Token,服务端验证其有效性(图左)。
若 Access Token 过期,前端使用 Refresh Token 请求服务端刷新 Token(图右)。
服务端校验 Refresh Token 的有效性:
-
若合法,生成新的 Access Token 与 Refresh Token 并返回;
-
若失效,提示用户重新登录。
二、生成双Token
在登录接口的LoginServiceImpl中,生成accessToken 和 refreshToken 并存到redis中:
long accessTokenExpire = 1000 * 60 * 15L; // 15分钟
long refreshTokenExpire = 1000 * 60 * 60 * 24 * 7L; // 7天
String accessToken = JwtUtils.sign(phone, accessTokenExpire);
String refreshToken = JwtUtils.sign(phone, refreshTokenExpire);
//将用户信息json化
LoginUserDetails principal = (LoginUserDetails) authenticate.getPrincipal();
String json = JSONUtil.toJsonStr(principal);
//保存到redis中
redisClient.set("login:token:" + accessToken, json, accessTokenExpire);
redisClient.set("login:refreshToken:" + refreshToken, json, refreshTokenExpire);
//将token返回给客户端
Map<String,Object> map = new HashMap<>();
map.put("token", accessToken);
map.put("refreshToken", refreshToken);
return map;
二、自定义刷新接口
当 Access Token 过期时,客户端需要携带 Refresh Token 调用 refresh 接口,获取新的 Token。
在refreshServiceImpl中:
- 验证 Refresh Token 是否存在 Redis
- 校验 Refresh Token 中的用户信息
- 重新生成新的 AccessToken 与 Refresh Token
- 存入 Redis,并删除旧的 Refresh Token
- 将新的 AccessToken 与 Refresh Token 返回给客户端
// 1. 验证refreshToken是否存在Redis
String redisKey = "login:refreshToken:" + refreshToken;
String userJson = redisClient.get(redisKey);
if (!StringUtils.hasText(userJson)) {
return Result.error("refreshToken无效或已过期,请重新登录");
}
// 2. 解析refreshToken中的手机号(验证用户信息)
String phone = JwtUtils.getClaim(refreshToken);
if (!StringUtils.hasText(phone)) {
return Result.error("refreshToken信息异常,请重新登录");
}
// 3. 根据手机号重新生成accessToken和refreshToken
long accessTokenExpire = 1000 * 60 * 15L; // 15分钟
long refreshTokenExpire = 1000 * 60 * 60 * 24 * 7L; // 7天
String newAccessToken = JwtUtils.sign(phone, accessTokenExpire);
String newRefreshToken = JwtUtils.sign(phone, refreshTokenExpire);
// 5. 将新token存入Redis,删除旧的Refreshtoken,旧的AccessToken过期了不用管
redisClient.del(redisKey);
redisClient.set("login:token:" + newAccessToken, userJson, accessTokenExpire);
redisClient.set("login:refreshToken:" + newRefreshToken, userJson, refreshTokenExpire);
// 6. 返回新token给客户端
Map<String, Object> map = new HashMap<>();
map.put("token", newAccessToken);
map.put("refreshToken", newRefreshToken);
return map;
三、修改LogOut处理器
前端需要在用户注销后,在请求头中传入Access Token 和 Refresh Token。而我们需要在spring-security过滤器链中,在我们自己实现的LogOut处理器中实现删除redis中的Access Token 和 Refresh Token的逻辑:
/**
* 注销成功后的处理器
*/
@Component
public class LogoutStatusSuccessHandler implements LogoutSuccessHandler {
@Resource
private RedisClient redisClient;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String token = request.getHeader("token");
String refreshToken = request.getHeader("refreshToken");
//清空redis中的 token
if(StringUtils.hasText(token)){
String key="login:token:"+token;
redisClient.del(key);
}
//清空redis中的 refreshToken
if(StringUtils.hasText(refreshToken)){
String key="login:refreshToken:"+refreshToken;
redisClient.del(key);
}
//返回给客户端注销成功的提示
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
Result result = Result.ok("注销成功");
String json = JSONUtil.toJsonStr(result);
response.getWriter().print(json);
}
}
四、总结
整体实现包含三个关键部分:
-
登录时生成双 Token 并保存到 Redis;
-
实现刷新接口,支持 Access Token 自动续期;
-
实现注销逻辑,确保 Token 在 Redis 中被清理。
更多推荐



所有评论(0)