一、整体实现流程

在这里插入图片描述
在实际业务中,用户频繁登录会带来不便,因此通常采用 双 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中:

  1. 验证 Refresh Token 是否存在 Redis
  2. 校验 Refresh Token 中的用户信息
  3. 重新生成新的 AccessToken 与 Refresh Token
  4. 存入 Redis,并删除旧的 Refresh Token
  5. 将新的 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 中被清理。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐