一、 JWT(JSON Web Token)
1.JWT 基本概念
JWT 是一种开放标准(RFC 7519),用于在网络应用间安全传递 JSON 格式的声明信息。其核心特点包括:
- 紧凑性:通过 Base64URL 编码生成字符串,可通过 URL、HTTP Header 或 POST 参数传输。
- 自包含:负载(Payload)直接携带用户信息(如 ID、角色),减少服务端查询数据库的开销。
- 数字签名:使用密钥(HMAC)或公钥/私钥(RSA)签名,确保信息未被篡改。
2.JWT 结构
JWT 由三部分组成,以点号 .
分隔:Header.Payload.Signature
。
-
Header(头部)
- 描述令牌类型(
typ: "JWT"
)和签名算法(如alg: HS256
),Base64URL 编码后形成第一部分。
json{ "alg": "HS256", "typ": "JWT" }
- 描述令牌类型(
-
Payload(负载)
- 存储声明(Claims),包含用户数据及标准声明(如
sub
用户 ID、exp
过期时间)。 - 声明类型 :
- 注册声明 (可选):预定义字段(
iss
签发者、aud
受众)。 - 公共声明 :自定义字段(如
name: "John"
)。 - 私有声明:系统内部使用的自定义字段。
- 注册声明 (可选):预定义字段(
注意 :Payload 仅 Base64URL 编码,未加密,避免存储敏感信息(如密码)。
- 存储声明(Claims),包含用户数据及标准声明(如
-
Signature(签名)
- 对编码后的 Header 和 Payload 用指定算法(如 HMAC-SHA256)签名,密钥由服务端保管:
jsHMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
- 签名验证数据完整性和来源真实性。
3.JWT 工作原理
- 认证阶段 :
- 用户提交凭证(用户名/密码)→ 服务端验证通过 → 生成 JWT 返回客户端。
- 客户端存储 :
- 客户端将 JWT 存入
localStorage
、sessionStorage
或 Cookie(建议 HttpOnly 防 XSS)。
- 客户端将 JWT 存入
- 请求携带 :
- 后续请求在 HTTP Header 中添加:
Authorization: Bearer <JWT>
。
- 后续请求在 HTTP Header 中添加:
- 服务端验证 :
- 校验签名有效性、过期时间(
exp
)及受众(aud
)→ 通过则允许访问资源。
- 校验签名有效性、过期时间(
4.JWT vs Session 认证
特性 | JWT | Session |
---|---|---|
服务端状态 | 无状态(信息在 Token 中) | 需存储会话信息(内存/数据库) |
扩展性 | 天然支持分布式系统 | 需 Session 共享机制(如 Redis) |
安全性 | 签名防篡改;但需防 XSS 盗取 Token | 依赖 Cookie;易受 CSRF 攻击 |
性能 | 减少数据库查询;但 Token 体积较大 | 需频繁查询会话数据 |
JWT 在微服务、跨域单点登录(SSO)场景中优势显著。
5.应用场景
- 身份认证:用户登录后,JWT 作为无状态凭证。
- 单点登录(SSO):一次登录生成 JWT,多系统共享认证状态。
二、Spring Security 集成 JWT
-
认证流程
- 用户登录:客户端提交凭证 → 服务端验证并生成 JWT
- 请求携带 :客户端在
Authorization: Bearer <token>
头中附加 JWT - 服务端验证:Spring Security 过滤器链解析并验证 JWT 有效性
-
技术优势
- 无状态架构:服务端无需存储会话,适合微服务分布式系统
- 细粒度授权:通过 JWT Claims 实现角色/权限动态控制
1.JWT 配置参数
yaml
jwt:
secret: "base64-encoded-secret" # Base64 编码密钥
expiration: 86400000 # Token 有效期(24小时)
header: Authorization # 请求头字段
2.Spring Security 配置
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 关闭 CSRF
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // 放行登录接口
.anyRequest().authenticated() // 其他接口需认证
.and()
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); // 添加 JWT 过滤器
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 密码加密器
}
}
3.JWT 工具类实现
java
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername()) // 用户名作为主体
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret) // HS512 算法签名
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
签名算法选择
算法类型 | 适用场景 |
---|---|
HS256/HS512 |
单应用场景(对称加密) |
RS256/RS512 |
多服务调用(非对称加密) |
4.认证流程实现
(1)登录接口生成 Token
java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
// 1. 验证用户名密码
UserDetails user = userDetailsService.loadUserByUsername(request.getUsername());
// 调用passwordEncoder验证密码是否一致
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
return ResponseEntity.status(401).build();
}
// 2. 生成 JWT
String token = jwtUtils.generateToken(user);
return ResponseEntity.ok(token);
}
}
新增用户时使用PasswordEncoder加密密码。
(2)JWT 认证过滤器
java
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response); // 放行未认证请求
return;
}
String token = header.substring(7);
Claims claims = jwtUtils.parseToken(token); // 解析 Token
// 构建 Authentication 对象并存入 SecurityContext
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
claims.getSubject(), null, AuthorityUtils.createAuthorityList("ROLE_USER")
);
SecurityContextHolder.getContext().setAuthentication(auth);
chain.doFilter(request, response);
}
}
在 Spring Security 中,UsernamePasswordAuthenticationToken 的 authorities 参数用于注入当前用户的权限集合(Collection<? extends GrantedAuthority>)。
6.常见问题与解决方案
(1)Token 失效场景
- 签名不匹配:检查密钥一致性(确保 Base64 编码正确)
- 过期时间(exp)未生效:服务器时间不同步 → 使用 NTP 同步
(2)跨域问题(CORS)
java
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOrigin("https://your-frontend.com");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}