基于Spring WebMVC和JWT的REST API安全防护实践
本文将详细介绍如何使用Spring Security和JWT(JSON Web Token)来保护REST API。我们将通过一个完整的示例项目(hantsy/spring-webmvc-jwt-sample)来演示如何实现自定义的JWT认证方案。
为什么选择JWT保护REST API
在构建RESTful服务时,安全性是必须考虑的重要因素。Spring Security提供了多种保护REST API的方式:
- HTTP Basic认证:最简单的方式,适合开发环境
- Session-based认证:使用Spring Session管理会话
- OAuth2认证:完整的OAuth2协议实现
对于自有应用(不向第三方开放API)而言,JWT提供了一种轻量级且自包含的认证方案。JWT的主要优势包括:
- 无状态:服务端不需要存储会话信息
- 自包含:所有必要信息都包含在令牌中
- 跨域支持:适合微服务架构
- 易于扩展:可以包含自定义声明
项目初始化与基础API开发
创建项目骨架
使用Spring Initializr创建项目时,需要选择以下依赖:
- Web:Spring Web MVC支持
- Security:Spring Security
- JPA:数据库访问
- Lombok:简化代码
开发车辆资源API
我们首先创建一个简单的车辆管理API:
@Entity
@Table(name="vehicles")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
对应的REST控制器提供了标准的CRUD操作:
@RestController
@RequestMapping("/v1/vehicles")
public class VehicleController {
@GetMapping("")
public ResponseEntity<List<Vehicle>> getAll() {
// 获取所有车辆
}
@PostMapping("")
public ResponseEntity<Void> create(@RequestBody VehicleForm form) {
// 创建新车辆
}
// 其他CRUD方法...
}
实现JWT认证
JWT核心组件
- JwtTokenProvider:负责JWT的创建、解析和验证
- JwtTokenFilter:拦截请求并验证JWT
- JwtConfigurer:将过滤器集成到Spring Security
JwtTokenProvider详解
@Component
public class JwtTokenProvider {
// 密钥和有效期配置
@Value("${security.jwt.token.secret-key:secret}")
private String secretKey;
@Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds;
// 创建JWT令牌
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
// 设置签发时间和过期时间
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// 其他关键方法...
}
JwtTokenFilter实现
public class JwtTokenFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
// 验证通过后设置认证信息
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(req, res);
}
}
安全配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/signin").permitAll()
.antMatchers(HttpMethod.GET, "/vehicles/**").permitAll()
.antMatchers(HttpMethod.DELETE, "/vehicles/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtTokenFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
}
}
用户认证流程
用户实体与存储
@Entity
@Table(name="users")
@Data
public class User implements UserDetails {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
@ElementCollection(fetch = EAGER)
private List<String> roles;
// 实现UserDetails接口方法...
}
认证端点
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/signin")
public ResponseEntity<Map<String, String>> signin(
@RequestBody AuthenticationRequest data) {
// 1. 验证用户名密码
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
data.getUsername(),
data.getPassword()));
// 2. 生成JWT令牌
User user = userRepository.findByUsername(data.getUsername())
.orElseThrow(...);
String token = jwtTokenProvider.createToken(
user.getUsername(),
user.getRoles());
// 3. 返回令牌
Map<String, String> model = new HashMap<>();
model.put("username", user.getUsername());
model.put("token", token);
return ResponseEntity.ok(model);
}
}
使用JWT访问受保护资源
客户端获取JWT后,需要在每次请求的Authorization头中携带:
Authorization: Bearer <JWT_TOKEN>
服务端会通过JwtTokenFilter验证令牌,并建立安全上下文。
测试与验证
- 首先获取JWT令牌:
curl -X POST http://localhost:8080/auth/signin \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
- 使用令牌访问受保护资源:
curl -X GET http://localhost:8080/v1/vehicles \
-H "Authorization: Bearer <JWT_TOKEN>"
总结
本文详细介绍了如何在Spring WebMVC应用中实现JWT认证方案,关键点包括:
- 自定义JWT令牌的生成与验证逻辑
- 实现Spring Security过滤器处理JWT
- 配置无状态的安全策略
- 提供用户认证端点
这种方案特别适合不需要复杂OAuth流程的自有应用,提供了轻量级且安全的API保护机制。开发者可以根据实际需求调整令牌的有效期、签名算法等参数,也可以扩展JWT的声明内容以满足业务需求。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考