Token相关设计

发布于:2025-03-01 ⋅ 阅读:(12) ⋅ 点赞:(0)

在微服务架构中,Token 认证是保障系统安全性的重要手段,常见的方式包括 JWT(JSON Web Token)基于 Redis 的 Token 认证。本文将介绍 双Token 机制 及其具体实现。


1. 双Token 机制概述

1.1 访问令牌(Access Token)

  • 用途:前端每次请求携带该令牌,用于身份认证。
  • 有效期:较短(如10分钟 - 2小时)。
  • 存储方式:前端存储(如 LocalStorage、SessionStorage、HTTP Only Cookie)。
  • 信息载荷:用户 ID、权限、过期时间等。

1.2 刷新令牌(Refresh Token)

  • 用途:用于获取新的 Access Token,避免用户频繁登录。
  • 有效期:较长(如7天 - 30天)。
  • 存储方式:数据库或 Redis(不能存储在前端,防止滥用)。
  • 信息载荷:用户 ID,仅用于重新获取 Access Token。

2. 双Token 认证流程

  1. 用户登录

    • 用户提交用户名、密码。
    • 后端校验通过后,生成 Access Token(短期) 和 Refresh Token(长期)。
    • Access Token 通过 JWT 方式返回给前端
    • Refresh Token 存储到数据库或 Redis,避免前端篡改。
  2. 请求 API 资源

    • 前端在每次请求时,携带 Authorization: Bearer <Access Token>
    • 后端解析 Access Token,校验有效性。
    • 通过则返回资源,失败则根据错误码处理(如 401 Unauthorized)。
  3. Token 过期时的处理

    • Access Token 过期,但 Refresh Token 仍有效

      • 前端调用 刷新接口,携带 Refresh Token 请求新 Access Token。
      • 后端验证 Refresh Token 后,重新生成 Access Token 并返回。
    • Refresh Token 过期或无效

      • 需要用户重新登录。
  4. 用户登出

    • 后端删除 Refresh Token 记录。
    • 前端删除 Access Token。

3. Spring Boot 具体实现

3.1 生成 Token(使用 JWT)

import io.jsonwebtoken.*;
import java.util.Date;

public class JwtUtil {
    private static final String SECRET_KEY = "your_secret_key";
    private static final long ACCESS_TOKEN_EXPIRATION = 2 * 60 * 60 * 1000; // 2小时
    private static final long REFRESH_TOKEN_EXPIRATION = 7 * 24 * 60 * 60 * 1000; // 7天

    // 生成 Access Token
    public static String generateAccessToken(String userId) {
        return Jwts.builder()
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    // 生成 Refresh Token
    public static String generateRefreshToken(String userId) {
        return Jwts.builder()
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

3.2 解析 Token

public static Claims parseToken(String token) {
    return Jwts.parser()
            .setSigningKey(SECRET_KEY)
            .parseClaimsJws(token)
            .getBody();
}

3.3 登录接口(返回双 Token)

@RestController
@RequestMapping("/auth")
public class AuthController {
    @PostMapping("/login")
    public Map<String, String> login(@RequestBody LoginRequest request) {
        // 1. 校验用户名和密码(省略)
        
        // 2. 生成 Token
        String accessToken = JwtUtil.generateAccessToken(request.getUsername());
        String refreshToken = JwtUtil.generateRefreshToken(request.getUsername());
        
        // 3. 存储 Refresh Token(示例使用 Redis)
        redisTemplate.opsForValue().set("refresh_token:" + request.getUsername(), refreshToken, 7, TimeUnit.DAYS);
        
        // 4. 返回 Token
        Map<String, String> tokens = new HashMap<>();
        tokens.put("accessToken", accessToken);
        tokens.put("refreshToken", refreshToken);
        return tokens;
    }
}

3.4 刷新 Token 接口

@PostMapping("/refresh")
public ResponseEntity<Map<String, String>> refresh(@RequestHeader("Authorization") String refreshToken) {
    // 1. 校验 Refresh Token
    Claims claims = JwtUtil.parseToken(refreshToken);
    String userId = claims.getSubject();

    // 2. 检查 Redis 是否存储该 Refresh Token
    String storedToken = redisTemplate.opsForValue().get("refresh_token:" + userId);
    if (storedToken == null || !storedToken.equals(refreshToken)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }

    // 3. 生成新的 Access Token
    String newAccessToken = JwtUtil.generateAccessToken(userId);
    
    // 4. 返回新的 Token
    Map<String, String> tokens = new HashMap<>();
    tokens.put("accessToken", newAccessToken);
    return ResponseEntity.ok(tokens);
}

3.5 退出登录

@PostMapping("/logout")
public ResponseEntity<Void> logout(@RequestHeader("Authorization") String accessToken) {
    Claims claims = JwtUtil.parseToken(accessToken);
    String userId = claims.getSubject();
    
    // 删除 Redis 中的 Refresh Token
    redisTemplate.delete("refresh_token:" + userId);
    return ResponseEntity.ok().build();
}

4. 总结 🚀

机制 作用 过期时间 存储位置
Access Token 用于 API 认证 短(10分钟-2小时) 前端 LocalStorage/SessionStorage
Refresh Token 用于刷新 Access Token 长(7天-30天) Redis/数据库
  • 双 Token 机制 既保证了安全性(短期有效的 Access Token)又提升了用户体验(长期有效的 Refresh Token)。
  • Access Token 过期时,通过 Refresh Token 重新获取,而无需重新登录。
  • Refresh Token 需要存储在后端(如 Redis),避免前端泄露。

博客主页: 总是学不会.


网站公告

今日签到

点亮在社区的每一天
去签到