基于Spring WebMVC和JWT的REST API安全防护实践

基于Spring WebMVC和JWT的REST API安全防护实践

spring-webmvc-jwt-sample Secures REST APIs with Spring Security and JWT Token based Authentication spring-webmvc-jwt-sample 项目地址: https://gitcode.com/gh_mirrors/sp/spring-webmvc-jwt-sample

本文将详细介绍如何使用Spring Security和JWT(JSON Web Token)来保护REST API。我们将通过一个完整的示例项目(hantsy/spring-webmvc-jwt-sample)来演示如何实现自定义的JWT认证方案。

为什么选择JWT保护REST API

在构建RESTful服务时,安全性是必须考虑的重要因素。Spring Security提供了多种保护REST API的方式:

  1. HTTP Basic认证:最简单的方式,适合开发环境
  2. Session-based认证:使用Spring Session管理会话
  3. 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核心组件

  1. JwtTokenProvider:负责JWT的创建、解析和验证
  2. JwtTokenFilter:拦截请求并验证JWT
  3. 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验证令牌,并建立安全上下文。

测试与验证

  1. 首先获取JWT令牌:
curl -X POST http://localhost:8080/auth/signin \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password"}'
  1. 使用令牌访问受保护资源:
curl -X GET http://localhost:8080/v1/vehicles \
  -H "Authorization: Bearer <JWT_TOKEN>"

总结

本文详细介绍了如何在Spring WebMVC应用中实现JWT认证方案,关键点包括:

  1. 自定义JWT令牌的生成与验证逻辑
  2. 实现Spring Security过滤器处理JWT
  3. 配置无状态的安全策略
  4. 提供用户认证端点

这种方案特别适合不需要复杂OAuth流程的自有应用,提供了轻量级且安全的API保护机制。开发者可以根据实际需求调整令牌的有效期、签名算法等参数,也可以扩展JWT的声明内容以满足业务需求。

spring-webmvc-jwt-sample Secures REST APIs with Spring Security and JWT Token based Authentication spring-webmvc-jwt-sample 项目地址: https://gitcode.com/gh_mirrors/sp/spring-webmvc-jwt-sample

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咎丹娜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值