Spring Security JWT
JWT 常用于前后端分离登录。服务端登录成功后签发 Token,客户端之后每次请求都带上 Token,服务端解析并建立认证上下文。
为什么前后端分离常用 JWT
传统 Session 需要服务端保存登录状态,浏览器通过 Cookie 带上 SessionId。前后端分离、移动端、多服务网关场景下,服务端之间共享 Session 会增加部署复杂度。JWT 把身份信息和签名放在 Token 中,服务端可以通过验签恢复身份,不一定每次都查 Session。
JWT 的代价是撤销不方便:Token 在过期前理论上都有效,所以退出登录、改密码、权限变更等场景通常要配合 Redis 黑名单、短 access token 和 refresh token。
登录流程
mermaid
sequenceDiagram
participant C as 客户端
participant S as 后端
participant U as UserDetailsService
participant J as JWT工具
C->>S: 用户名密码登录
S->>U: 查询用户和权限
U-->>S: 用户信息
S->>S: 校验密码
S->>J: 生成 access token
J-->>S: token
S-->>C: 返回 token
C->>S: 携带 Authorization 请求接口
S->>J: 解析 token
S->>S: 写入 SecurityContextToken 内容
| 字段 | 说明 |
|---|---|
| subject | 用户唯一标识 |
| expireTime | 过期时间 |
| authorities | 权限或角色 |
| issuer | 签发方 |
| jti | token 唯一 ID,可用于黑名单 |
过滤器位置
JWT 解析过滤器通常放在用户名密码过滤器之前。它的职责是读取请求头、解析 Token、校验有效性、构造 Authentication。
mermaid
flowchart TD
A["请求进入"] --> B["JWT 过滤器"]
B --> C{"Token 有效?"}
C -- "是" --> D["写入 SecurityContext"]
C -- "否" --> E["匿名或拒绝"]
D --> F["权限校验"]安全建议
- access token 有效期要短,refresh token 单独管理。
- 退出登录时可把 token id 写入黑名单,直到自然过期。
- 密钥不要硬编码,生产环境使用配置中心或密钥管理服务。
- 不要在 JWT 中放手机号、身份证、密码等敏感信息。
- 权限变化频繁的系统,不要完全依赖长期 JWT 中的权限快照。
代码 Demo:JWT 过滤器
java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenService jwtTokenService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = authorization.substring("Bearer ".length());
if (!jwtTokenService.validate(token)) {
filterChain.doFilter(request, response);
return;
}
String userId = jwtTokenService.getSubject(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}注册过滤器时通常放在 UsernamePasswordAuthenticationFilter 前面:
java
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);常见风险
| 风险 | 后果 | 建议 |
|---|---|---|
| Token 有效期过长 | 泄露后长期可用 | access token 短期有效,refresh token 单独管理 |
| JWT 存敏感信息 | 客户端可解码看到 payload | 只放用户标识、权限摘要和必要声明 |
| 权限长期写进 Token | 权限变更后旧 Token 仍有效 | 关键权限实时查库或使用 token 版本号 |
| 不校验签名和过期时间 | 伪造 Token 可通过 | 统一封装验证逻辑 |
| 退出登录不做失效处理 | 用户退出后旧 Token 仍可用 | 使用黑名单或刷新令牌轮换 |
