Spring Boot集成JWT:打造安全的RESTful API
Spring Boot集成JWT:打造安全的RESTful API
1. JWT简介
JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在各方之间安全传输信息。它广泛应用于身份认证和授权场景,尤其适合无状态的RESTful API开发。
JWT的组成:
- Header:声明令牌类型(如
JWT
)和签名算法(如HS256
)。{ "alg": "HS256", "typ": "JWT" }
- Payload:存储用户信息(如用户ID、角色)和其他元数据(如过期时间)。
{ "sub": "admin", "iat": 1625145600, "exp": 1625232000 }
- Signature:通过密钥对Header和Payload进行加密生成的签名,确保数据未被篡改。
JWT的优势:
- 无状态:服务端无需存储会话信息,减轻服务器压力。
- 跨语言支持:几乎所有编程语言都有JWT实现库。
- 易于扩展:Payload可灵活添加自定义字段。
2. 环境准备
步骤1:创建Spring Boot项目
使用 Spring Initializr 生成项目,勾选以下依赖:
- Spring Web
- Spring Security
步骤2:添加JJWT依赖(Maven)
<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>
步骤3:配置文件
# application.properties
# JWT配置
jwt.secret=MySuperSecretKey123!@# # 密钥(需强密码)
jwt.expiration=86400000 # 有效期24小时(单位:毫秒)
3. 核心实现
3.1 JWT工具类
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration}")
private long expiration;
// 生成令牌
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// 验证令牌
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// 解析用户名
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
3.2 Spring Security配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtUtils jwtUtils;
public SecurityConfig(JwtUtils jwtUtils) {
this.jwtUtils = jwtUtils;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
// JWT验证过滤器
private Filter jwtFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtils.validateToken(token)) {
String username = jwtUtils.getUsernameFromToken(token);
Authentication auth = new UsernamePasswordAuthenticationToken(
username, null, Collections.emptyList()
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
};
}
}
3.3 登录接口
@RestController
@RequestMapping("/api")
public class AuthController {
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 实际项目中应查询数据库验证用户
if ("admin".equals(request.getUsername())
&& "admin123".equals(request.getPassword())) {
String token = jwtUtils.generateToken(request.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
@Data
class LoginRequest {
private String username;
private String password;
}
@Data
class JwtResponse {
private String token;
public JwtResponse(String token) {
this.token = token;
}
}
4. 高级配置
4.1 自定义异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {
ExpiredJwtException.class,
MalformedJwtException.class,
SignatureException.class
})
public ResponseEntity<?> handleJwtException(Exception ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", ex.getMessage()));
}
}
4.2 基于角色的权限控制
@GetMapping("/admin/dashboard")
@PreAuthorize("hasRole('ADMIN')")
public String adminDashboard() {
return "Admin Access Granted";
}
4.3 跨域配置
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
5. 常见问题与解决方案
问题1:令牌解析失败
- 可能原因:
- 密钥不匹配
- 令牌过期
- 令牌被篡改
- 排查步骤:
- 检查
jwt.secret
配置是否一致 - 使用 jwt.io 调试令牌
- 检查
问题2:令牌刷新机制
- 实现方案:
- 客户端在令牌过期前调用
/api/refresh
接口 - 服务端验证旧令牌有效性后生成新令牌
- 客户端在令牌过期前调用
问题3:Spring Security兼容性
- 典型错误:
Cannot convert access token to JSON
- 解决方案:确保依赖版本一致,检查
jjwt-impl
是否包含在运行时依赖中
6. 总结与扩展
应用场景:
- 微服务间的安全通信
- 移动应用用户认证
- 前后端分离架构的权限管理
扩展学习:
- JWT官方文档
- Spring Security官方指南
- 实战案例:GitHub电商项目集成JWT
流程图:JWT认证流程
避坑指南:
- 密钥安全:切勿硬编码密钥,应通过环境变量注入
- 令牌存储:客户端使用
HttpOnly
Cookie或SecureLocalStorage
- 时间同步:确保服务器间时钟同步,避免令牌有效期计算错误
通过本指南,您已掌握在Spring Boot中集成JWT的核心技术。立即动手实践,为您的API穿上安全盔甲! 🔐