You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot结合Flutter的JWT登录/登出令牌过期处理方案咨询

JWT登录/登出完整实现方案

核心结论

仅靠JWT本身无法实现你的需求,必须结合额外的服务端存储机制(比如Redis)来解决令牌续期和即时失效的问题。JWT是无状态的,一旦签发就无法主动作废,且过期时间硬编码在令牌内,所以需要补充以下机制:

一、解决令牌自动续期(问题a)

方案:双令牌机制(Access Token + Refresh Token)

通过长短令牌配合,既保证安全性,又实现用户未登出时的持续授权:

  • Access Token:有效期短(如15分钟),用于日常API请求的身份验证
  • Refresh Token:有效期长(如7天),仅用于在Access Token过期时获取新的Access Token

实现步骤

  1. 用户登录验证通过后,服务器同时签发Access Token和Refresh Token,返回给Flutter客户端
  2. 客户端将两个令牌持久化存储(比如用shared_preferences),每次API请求携带Access Token
  3. 当服务器返回401 Unauthorized(Access Token过期),客户端自动调用刷新令牌接口,用Refresh Token换取新的Access Token
  4. 服务器验证Refresh Token:
    • 校验签名和自身过期时间
    • 确认该Refresh Token未被服务器删除(和登出逻辑联动)
    • 验证通过后,签发新的Access Token(可选同时刷新Refresh Token并更新存储)

核心代码示例

// 登录接口返回双令牌
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
    // 省略用户身份验证逻辑
    User user = userService.validateUser(request.getUsername(), request.getPassword());
    
    String accessToken = jwtUtil.generateAccessToken(user);
    String refreshToken = jwtUtil.generateRefreshToken(user);
    
    // 将Refresh Token存入Redis,关联用户ID并设置过期时间
    redisTemplate.opsForValue().set("refresh_token:" + user.getId(), refreshToken, 7, TimeUnit.DAYS);
    
    return ResponseEntity.ok(new AuthResponse(accessToken, refreshToken));
}

// 刷新令牌接口
@PostMapping("/refresh")
public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshTokenRequest request) {
    String refreshToken = request.getRefreshToken();
    if (!jwtUtil.isValidRefreshToken(refreshToken)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
    
    String userId = jwtUtil.extractUserId(refreshToken);
    String storedRefreshToken = redisTemplate.opsForValue().get("refresh_token:" + userId);
    
    // 验证Refresh Token是否有效(未被登出删除)
    if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
    
    User user = userService.getById(userId);
    String newAccessToken = jwtUtil.generateAccessToken(user);
    // 可选:生成新的Refresh Token并更新Redis
    String newRefreshToken = jwtUtil.generateRefreshToken(user);
    redisTemplate.opsForValue().set("refresh_token:" + userId, newRefreshToken, 7, TimeUnit.DAYS);
    
    return ResponseEntity.ok(new AuthResponse(newAccessToken, newRefreshToken));
}

二、解决登出即时失效(问题b)

方案:令牌黑名单机制(基于Redis)

由于JWT无法主动作废,需要在服务器端维护黑名单记录已失效的令牌:

实现步骤

  1. 用户发起登出请求时,客户端将Access Token和Refresh Token发送到服务器
  2. 服务器将Access Token加入黑名单,设置过期时间为该令牌的剩余有效期(避免永久占用存储)
  3. 同时删除Redis中存储的对应Refresh Token
  4. 每次API请求验证时,先检查Access Token是否在黑名单中,若存在则直接返回未授权

核心代码示例

@PostMapping("/logout")
public ResponseEntity<Void> logout(@RequestHeader("Authorization") String authHeader) {
    String accessToken = authHeader.replace("Bearer ", "");
    
    // 计算令牌剩余有效期,设置黑名单过期时间
    Long remainingTime = jwtUtil.extractExpiration(accessToken).getTime() - System.currentTimeMillis();
    redisTemplate.opsForValue().set("blacklist:" + accessToken, "invalid", remainingTime, TimeUnit.MILLISECONDS);
    
    // 删除对应的Refresh Token
    String userId = jwtUtil.extractUserId(accessToken);
    redisTemplate.delete("refresh_token:" + userId);
    
    return ResponseEntity.ok().build();
}

// 在JWT验证过滤器中添加黑名单检查
public class JwtAuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String accessToken = extractTokenFromRequest(request);
        if (accessToken != null && jwtUtil.isValidAccessToken(accessToken)) {
            // 检查令牌是否在黑名单中
            if (redisTemplate.hasKey("blacklist:" + accessToken)) {
                response.sendError(HttpStatus.UNAUTHORIZED.value(), "Token has been invalidated");
                return;
            }
            // 后续身份验证逻辑(加载用户信息、设置SecurityContext等)
        }
        chain.doFilter(request, response);
    }
}

整体流程梳理

  1. 登录:用户提交账号密码,服务器验证后返回双令牌,同时将Refresh Token存入Redis
  2. API请求:客户端携带Access Token,服务器依次验证签名、有效期、黑名单状态,通过则返回业务数据
  3. 令牌过期:Access Token过期后,客户端自动调用刷新接口,服务器验证Refresh Token有效性后返回新的Access Token
  4. 登出:客户端发送令牌到登出接口,服务器将Access Token加入黑名单并删除Refresh Token,后续该令牌无法再使用

注意事项

  • 必须使用支持过期时间的键值存储,Redis是最优选择,轻量且性能高效
  • Flutter端需在拦截器中自动处理401错误,触发令牌刷新逻辑;同时注意令牌的安全存储(比如Refresh Token存入移动端Keystore)
  • 可根据业务需求调整双令牌的有效期,平衡安全性和用户体验

内容的提问来源于stack exchange,提问作者ayush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:20:28