Spring Boot整合Spring Security与JWT实现无状态认证:实战指南
一、JWT认证原理简介
JSON Web Token(JWT)是一种开放标准(RFC 7519),由三部分组成:
- Header(头部):声明令牌类型和签名算法
- Payload(负载):携带用户身份信息
- Signature(签名):防篡改验证
认证流程:
- 客户端提交登录凭证
- 服务端验证通过后生成JWT
- 客户端后续请求携带JWT
- 服务端验证JWT有效性
二、项目搭建与依赖准备
1. 创建Spring Boot项目
选择依赖:
- Spring Web
- Spring Security
- Lombok
- Spring Data JPA(数据库存储)
2. 添加JWT依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
三、核心组件实现
1. JWT工具类
public class JwtUtils {private static final String SECRET_KEY = "your-256-bit-secret";private static final long EXPIRATION = 86400000; // 24小时public static String generateToken(UserDetails userDetails) {return Jwts.builder().setSubject(userDetails.getUsername()).claim("roles", userDetails.getAuthorities()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)).signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();}public static Claims parseToken(String token) {return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();}public static boolean validateToken(String token, UserDetails userDetails) {final String username = extractUsername(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}private static boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}// 其他辅助方法...
}
2. 自定义UserDetails实现
@Entity
@Data
public class User implements UserDetails {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;@ManyToMany(fetch = FetchType.EAGER)private Set<Role> roles;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());}// 实现其他UserDetails方法...
}
四、安全配置类
1. Spring Security配置
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final JwtAuthenticationFilter jwtAuthFilter;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**").permitAll().anyRequest().authenticated()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
2. JWT认证过滤器
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {private final JwtUtils jwtUtils;private final UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {final String authHeader = request.getHeader("Authorization");if (authHeader == null || !authHeader.startsWith("Bearer ")) {filterChain.doFilter(request, response);return;}try {final String jwt = authHeader.substring(7);final String username = jwtUtils.extractUsername(jwt);if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (jwtUtils.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authToken);}}} catch (Exception e) {logger.error("Cannot set user authentication", e);}filterChain.doFilter(request, response);}
}
五、认证控制器实现
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {private final AuthenticationManager authenticationManager;private final JwtUtils jwtUtils;private final UserService userService;@PostMapping("/login")public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest request) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(),request.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);UserDetails userDetails = (UserDetails) authentication.getPrincipal();String jwt = jwtUtils.generateToken(userDetails);return ResponseEntity.ok(new JwtResponse(jwt));}@PostMapping("/register")public ResponseEntity<?> registerUser(@RequestBody RegisterRequest request) {if (userService.existsByUsername(request.getUsername())) {return ResponseEntity.badRequest().body("用户名已存在");}User user = new User();user.setUsername(request.getUsername());user.setPassword(passwordEncoder.encode(request.getPassword()));userService.saveUser(user);return ResponseEntity.ok("用户注册成功");}
}
六、测试验证
使用Postman测试流程:
- 注册用户
POST /api/auth/register
{"username": "testuser","password": "Test@1234"
}
- 登录获取Token
POST /api/auth/login
{"username": "testuser","password": "Test@1234"
}响应:
{"token": "eyJhbGciOiJIUzI1NiJ9.xxxxxx"
}
- 访问受保护接口
GET /api/protected
Headers: Authorization: Bearer <your-token>
七、安全增强措施
- 密钥管理:
// 推荐从环境变量读取密钥
private static final String SECRET_KEY = System.getenv("JWT_SECRET");
- 刷新令牌机制:
@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {String refreshToken = jwtUtils.getRefreshTokenFromRequest(request);// 验证刷新令牌并颁发新访问令牌
}
- 黑名单机制:
// 登出时将令牌加入黑名单
public void logout(String token) {long expiration = jwtUtils.getExpirationFromToken(token);long currentTime = System.currentTimeMillis();if (expiration > currentTime) {blacklistService.addToBlacklist(token, expiration - currentTime);}
}
八、最佳实践建议
- 使用HTTPS传输JWT
- 设置合理的令牌有效期(访问令牌1小时,刷新令牌7天)
- 存储敏感信息在服务端,Payload只放必要信息
- 定期轮换签名密钥
- 实现令牌吊销机制
- 监控异常认证尝试
- 使用强密码策略(至少8位,包含大小写字母、数字和特殊字符)
结语
通过本实践案例,我们实现了基于JWT的无状态认证系统。实际开发中还需要考虑:
- 分布式系统的会话管理
- 微服务架构中的令牌传递
- 第三方登录集成(OAuth2)
- 审计日志记录
建议结合具体业务需求调整安全策略,并定期进行安全渗透测试。Spring Security与JWT的结合为现代Web应用提供了灵活强大的安全解决方案,正确实施可以有效保护系统资源。