[Java实战]Spring Boot 3整合JWT实现无状态身份认证(二十四)
一、JWT简介与核心概念
1. JWT是什么?
JSON Web Token (JWT) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT由三部分组成:
- Header:描述令牌类型和签名算法(如HS256、RS256)。
- Payload:包含用户身份信息(如用户ID、角色)和其他声明(如过期时间)。
- Signature:对前两部分的签名,确保令牌未被篡改。
2. 为什么要用JWT?
- 无状态:服务端无需存储会话信息,适合分布式系统。
- 跨域支持:可通过HTTP头部轻松传递,适用于前后端分离架构。
- 安全性:基于签名验证,防止数据篡改。
二、环境准备
1. 创建Spring Boot 3.4.5项目
通过 Spring Initializr 创建项目,选择以下依赖:
- Spring Web
- Spring Security
- Lombok(简化代码)
2. 添加JWT依赖
在 pom.xml
中添加 jjwt
库:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
三、核心代码实现
1. JWT工具类
/**
* JwtUtils - 类功能描述
*
* @author csdn:曼岛_
* @version 1.0
* @date 2025/5/14 15:00
* @since openJDK 17
*/
@Component
public class JwtUtils {
// 密钥(示例,实际应使用安全随机生成并妥善存储)
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 86400000; // 24小时(毫秒)
// 生成JWT令牌
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.claims(claims)
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY)
.compact();
}
// 解析用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 验证令牌有效性
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 检查令牌是否过期
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// 提取过期时间(新增方法)
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// 提取声明
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// 解析全部声明
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(SECRET_KEY)
.build()
.parseSignedClaims(token)
.getPayload();
}
// 从请求头获取令牌
public String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
四、集成Spring Security
1. 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
public SecurityConfig(JwtAuthFilter jwtAuthFilter) {
this.jwtAuthFilter = jwtAuthFilter;
}
@Bean
public 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();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance(); // 临时使用 NoOpPasswordEncoder
// return new BCryptPasswordEncoder(); //生产环境这样用
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config
) throws Exception {
return config.getAuthenticationManager();
}
}
2. 实现JWT认证过滤器
/**
* JwtAuthFilter - 类功能描述
*
* @author csdn:曼岛_
* @version 1.0
* @date 2025/5/14 15:04
* @since openJDK 17
*/
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final CustomUserDetailsService userDetailsService;
public JwtAuthFilter(JwtUtils jwtUtils, UserDetailsService userDetailsService) {
this.jwtUtils = jwtUtils;
this.userDetailsService = (CustomUserDetailsService) userDetailsService;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws IOException, ServletException {
String token = jwtUtils.getTokenFromRequest(request);
if (token != null) {
String username = jwtUtils.extractUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
五、创建认证接口
1. 用户登录请求DTO
public record LoginRequest(String username, String password) {}
2. 认证控制器
/**
* AuthController - 类功能描述
*
* @author csdn:曼岛_
* @version 1.0
* @date 2025/5/14 15:07
* @since openJDK 17
*/
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
public AuthController(
AuthenticationManager authenticationManager,
JwtUtils jwtUtils
) {
this.authenticationManager = authenticationManager;
this.jwtUtils = jwtUtils;
}
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
String username = request.username();
String password = request.password();
System.out.println("=====username====="+username);
System.out.println("=====password====="+password);
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.username(),
request.password()
)
);
// 设置安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 从 Authentication 对象中提取 UserDetails
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 生成 JWT 令牌
String token = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(token);
}
}
六、测试验证
1. 登录获取Token
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"password"}'
2. 访问受保护接口
curl http://localhost:8080/api/protected \
-H "Authorization: Bearer <your-jwt-token>"
七、常见问题与解决方案
1. SignatureException: JWT signature does not match
- 原因:密钥不匹配或令牌被篡改。
- 解决:确保服务端密钥一致且安全存储。
2. ExpiredJwtException
- 原因:令牌已过期。
- 解决:重新登录获取新令牌,或实现令牌刷新机制。
3. Spring Security配置不生效
- 检查点:确保
@EnableWebSecurity
注解已添加,过滤器链配置正确。
八、总结
通过本文,已完成以下关键步骤:
- 集成
jjwt
库实现JWT生成与解析。 - 配置Spring Security实现无状态认证。
- 创建登录接口颁发JWT令牌。
- 通过过滤器验证请求中的JWT令牌。
扩展建议:
- 添加角色权限控制(
@PreAuthorize
)。 - 实现令牌刷新机制。
- 使用HTTPS增强传输安全性。
如果你在使用过程中遇到任何问题,欢迎在评论区留言交流。感谢你的阅读,希望这篇文章对你有所帮助!