跳到主要内容

JWT 认证详解

项目使用 JJWT 0.13.0 实现 JWT 认证,Token 中包含用户名、用户ID和角色信息。


🔧 配置

application.yaml
app:
security:
jwt-secret: ${JWT_SECRET} # 至少 256 位
jwt-expiration: 7200000 # 2小时(毫秒)
SecurityProperties.java
@Data
@Configuration
@ConfigurationProperties(prefix = "app.security")
public class SecurityProperties {
private String jwtSecret;
private Long jwtExpiration = 7200000L;
}

🔑 JwtTokenProvider

位置: blog-common/src/main/java/com/blog/common/security/JwtTokenProvider.java

生成 Token

public String generateToken(UserDetails userDetails, Long userId) {
Map<String, Object> claims = new HashMap<>();

// 提取角色
List<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());

claims.put("roles", roles);
claims.put("userId", userId);

return Jwts.builder()
.claims(claims)
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSigningKey())
.compact();
}

Token 结构

{
"sub": "testuser", // 用户名
"userId": 1, // 用户ID
"roles": ["ROLE_USER"], // 角色列表
"iat": 1702382400, // 签发时间
"exp": 1702389600 // 过期时间
}

验证 Token

public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token);
return true;
} catch (Exception e) {
log.error("JWT Token 验证失败: {}", e.getMessage());
return false;
}
}

提取信息

// 提取用户名
public String getUsernameFromToken(String token) {
return getClaimsFromToken(token).getSubject();
}

// 提取用户ID
public Long getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
Object userId = claims.get("userId");
if (userId instanceof Integer) {
return ((Integer) userId).longValue();
}
return (Long) userId;
}

// 提取角色
public List<String> getRolesFromToken(String token) {
return (List<String>) getClaimsFromToken(token).get("roles");
}

🔍 JwtAuthenticationFilter

位置: blog-application/src/main/java/com/blog/security/JwtAuthenticationFilter.java

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider tokenProvider;

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

try {
// 1. 从请求头提取 Token
String jwt = getJwtFromRequest(request);

// 2. 验证 Token
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
// 3. 解析用户信息
String username = tokenProvider.getUsernameFromToken(jwt);
Long userId = tokenProvider.getUserIdFromToken(jwt);
List<String> roles = tokenProvider.getRolesFromToken(jwt);

// 4. 构建权限列表
List<GrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

// 5. 创建认证对象
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, authorities);
authentication.setDetails(
new JwtAuthenticationDetails(request, userId));

// 6. 设置到 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
log.error("无法设置用户认证: {}", e.getMessage());
}

filterChain.doFilter(request, response);
}

private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

📦 JwtAuthenticationDetails

存储额外的用户信息(如 userId):

@Getter
public class JwtAuthenticationDetails extends WebAuthenticationDetails {

private final Long userId;

public JwtAuthenticationDetails(HttpServletRequest request, Long userId) {
super(request);
this.userId = userId;
}
}

🔐 使用示例

登录获取 Token

curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"Password123!"}'
{
"code": 0,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"tokenType": "Bearer",
"expiresIn": 7200
}
}

携带 Token 访问 API

curl -X GET http://localhost:8080/api/v1/users/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

⚠️ 安全最佳实践

1. JWT Secret 配置

# 生成安全的 256 位密钥
openssl rand -base64 32

# 设置环境变量
export JWT_SECRET="生成的密钥"
生产环境
  • 必须通过环境变量注入 JWT_SECRET
  • 密钥长度至少 256 位(32 字节)
  • 不要硬编码在配置文件中

2. Token 传递

# ✅ 正确:使用 Authorization Header
Authorization: Bearer eyJhbGciOiJ...

# ❌ 错误:URL 参数传递
?token=eyJhbGciOiJ...

3. JJWT API 版本

项目使用 JJWT 0.12+ 现代 API:

新 API旧 API(已废弃)
Keys.hmacShaKeyFor().setSigningKey(String)
.verifyWith().setSigningKey()
.parseSignedClaims().parseClaimsJws()

📚 延伸阅读