1. SpringBoot与JWT整合的核心价值
现代Web应用开发中,认证授权是绕不开的话题。传统基于Session的认证方式在分布式场景下会面临会话保持、跨域共享等问题,而JWT(JSON Web Token)作为一种无状态的认证机制,恰好能解决这些痛点。我在多个微服务项目中实践发现,SpringBoot与JWT的组合能实现:
- 服务端无状态化:不需要存储会话信息,减轻服务器压力
- 跨域认证支持:天然支持前后端分离架构
- 标准化输出:规范的Token格式便于多语言解析
- 微服务友好:适合作为分布式系统的统一认证方案
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境搭建
2.1 依赖配置
首先在pom.xml中添加必要依赖:
xml复制<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>
注意:JJWT库的API和实现需要分开引入,避免版本冲突问题
2.2 配置文件
在application.yml中配置JWT参数:
yaml复制jwt:
secret: mySecretKey # 加密密钥
expiration: 86400 # token有效期(秒)
header: Authorization # 请求头字段
3. JWT工具类实现
3.1 Token生成逻辑
创建JwtTokenUtil工具类:
java复制public class JwtTokenUtil {
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
// 生成token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
}
3.2 Token解析验证
在工具类中添加验证方法:
java复制public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
4. Spring Security整合
4.1 安全配置类
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
4.2 JWT认证过滤器
java复制public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String tokenPrefix = "Bearer ";
if (authHeader != null && authHeader.startsWith(tokenPrefix)) {
String authToken = authHeader.substring(tokenPrefix.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
5. 认证接口实现
5.1 登录接口
java复制@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
}
5.2 用户注册接口
java复制@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
if(userRepository.existsByUsername(registerRequest.getUsername())) {
return ResponseEntity.badRequest().body("用户名已存在");
}
User user = new User();
user.setUsername(registerRequest.getUsername());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setRoles("ROLE_USER");
userRepository.save(user);
return ResponseEntity.ok("注册成功");
}
6. 实战经验与优化建议
6.1 Token刷新机制
实际项目中建议实现token刷新机制:
java复制@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
String authToken = request.getHeader("Authorization").substring(7);
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (jwtTokenUtil.canTokenBeRefreshed(authToken)) {
String refreshedToken = jwtTokenUtil.refreshToken(authToken);
return ResponseEntity.ok(new JwtResponse(refreshedToken));
} else {
return ResponseEntity.badRequest().body("Token无法刷新");
}
}
6.2 黑名单处理
对于需要提前失效的token,可以采用Redis黑名单方案:
java复制@Autowired
private RedisTemplate<String, String> redisTemplate;
public void invalidateToken(String token) {
long expiration = jwtTokenUtil.getExpirationFromToken(token).getTime();
long currentTime = System.currentTimeMillis();
if(expiration > currentTime) {
redisTemplate.opsForValue().set(token, "invalid",
expiration - currentTime, TimeUnit.MILLISECONDS);
}
}
6.3 性能优化技巧
- 密钥轮换:定期更换JWT签名密钥,增强安全性
- 负载优化:不要在token中存储过多claim,影响传输效率
- 双Token方案:使用accessToken+refreshToken组合,平衡安全性与用户体验
7. 常见问题排查
7.1 Token验证失败
- 现象:返回401未授权错误
- 排查步骤:
- 检查请求头是否包含Authorization字段
- 验证token是否以"Bearer "开头
- 检查token是否过期(exp claim)
- 验证签名是否匹配(secret key是否正确)
7.2 跨域问题
- 解决方案:添加CORS配置
java复制@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
7.3 性能监控
建议添加监控端点统计认证情况:
java复制@RestController
@RequestMapping("/api/monitor")
public class MonitorController {
@Autowired
private CounterService counterService;
@PostMapping("/auth")
public void recordAuth(@RequestParam boolean success) {
if(success) {
counterService.increment("auth.success");
} else {
counterService.increment("auth.failure");
}
}
}
8. 安全增强措施
- HTTPS强制:生产环境必须启用HTTPS
- Token存储:前端应使用HttpOnly的Cookie存储
- 密钥保护:避免将密钥硬编码在代码中,推荐使用配置中心
- 防重放攻击:添加jti(唯一标识)和nonce(随机数)校验
我在实际项目中发现,合理的token过期时间设置(如accessToken 2小时,refreshToken 7天)能在安全性和用户体验间取得良好平衡。同时建议对敏感接口添加二次验证机制,如短信验证码等。
