文章目录
在微服务架构中,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 认证流程
用户登录
- 用户提交用户名、密码。
- 后端校验通过后,生成 Access Token(短期) 和 Refresh Token(长期)。
- Access Token 通过 JWT 方式返回给前端。
- Refresh Token 存储到数据库或 Redis,避免前端篡改。
请求 API 资源
- 前端在每次请求时,携带
Authorization: Bearer <Access Token>
。 - 后端解析 Access Token,校验有效性。
- 通过则返回资源,失败则根据错误码处理(如
401 Unauthorized
)。
- 前端在每次请求时,携带
Token 过期时的处理
Access Token 过期,但 Refresh Token 仍有效
:
- 前端调用 刷新接口,携带 Refresh Token 请求新 Access Token。
- 后端验证 Refresh Token 后,重新生成 Access Token 并返回。
Refresh Token 过期或无效
:
- 需要用户重新登录。
用户登出
- 后端删除 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),避免前端泄露。
博客主页: 总是学不会.