在构建现代化的RESTful API时,安全性至关重要。SpringBoot 12 配合 JSON Web Token (JWT) 是一种常用的身份验证和授权机制,可以有效保护你的API接口免受未经授权的访问。本文将深入探讨如何在SpringBoot项目中集成JWT,并分享一些实战中的避坑经验。
JWT 原理与优势
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于安全地传输JSON对象作为通信双方之间的令牌。JWT通常用于身份验证和授权,因为它可以包含用户的身份信息和权限声明,并且可以使用数字签名进行验证,确保令牌的完整性和真实性。相比于传统的Session认证方式,JWT具有无状态、可扩展性强等优点,尤其适合于分布式系统和微服务架构。
- 无状态: 服务器无需存储会话信息,减轻了服务器的负担,更容易扩展。
- 可扩展性强: JWT包含所有必要信息,可以独立验证,易于在分布式系统中使用。
- 跨域支持: JWT天然支持跨域认证,可以方便地构建跨域应用程序。
SpringBoot 集成 JWT 的步骤
添加 JWT 依赖:

在
pom.xml文件中添加jjwt依赖。这里需要注意版本兼容性,避免出现依赖冲突。另外,如果你在使用Spring Security,还需要添加Spring Security相关的依赖。<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> <!-- 选择与SpringBoot版本兼容的版本 --> </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>定义 JWT 工具类:

创建一个JWT工具类,用于生成、验证和解析JWT。这个类通常包含以下方法:
generateToken(UserDetails userDetails): 生成JWT。validateToken(String token, UserDetails userDetails): 验证JWT的有效性。getUsernameFromToken(String token): 从JWT中获取用户名。
@Component public class JwtUtil { @Value("${jwt.secret}") // 从application.properties中读取密钥 private String secret; @Value("${jwt.expiration}") // 从application.properties中读取过期时间 private long expiration; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } // 其他方法省略... }配置 Spring Security:

如果你使用了Spring Security,需要配置一个JWT过滤器,用于拦截请求并验证JWT。该过滤器需要在
UsernamePasswordAuthenticationFilter之前执行。如果你没有使用Spring Security,则需要手动实现请求拦截和JWT验证逻辑。@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtRequestFilter jwtRequestFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/authenticate").permitAll() // 允许/authenticate接口匿名访问 .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 设置为无状态Session http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 } }创建 JWT 过滤器:

创建一个JWT过滤器,用于从请求头中获取JWT,验证JWT的有效性,并将用户信息放入Spring Security的上下文中。
@Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.getUsernameFromToken(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } }创建用户认证接口:
创建一个
/authenticate接口,用于接收用户名和密码,验证用户信息,并生成JWT。这里可以对接数据库或者其他的用户数据源进行身份验证。@RestController public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailsService userDetailsService; @PostMapping("/authenticate") public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()) ); } catch (DisabledException e) { throw new Exception("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new Exception("INVALID_CREDENTIALS", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String jwt = jwtUtil.generateToken(userDetails); return ResponseEntity.ok(new AuthenticationResponse(jwt)); } }
实战避坑经验
- 密钥安全: 密钥(
secret)的安全性至关重要。务必使用高强度的随机字符串作为密钥,并妥善保管,避免泄露。 可以考虑使用环境变量或者外部化配置来管理密钥。 - 过期时间设置: 合理设置JWT的过期时间,避免令牌被长期滥用。 过期时间过短会导致用户频繁重新登录,影响用户体验;过期时间过长会增加安全风险。
- 刷新Token: 当JWT过期时,可以使用刷新Token机制来获取新的JWT,而无需用户重新登录。 刷新Token需要单独存储,并需要额外的安全保护。
- 防止JWT注入攻击: 对JWT进行签名验证,确保JWT没有被篡改。 在服务器端验证JWT的有效性,不要完全信任客户端传递的JWT。
- 考虑使用HTTPS: 使用HTTPS协议来传输JWT,防止JWT在传输过程中被窃取。
- Nginx反向代理与负载均衡: 在高并发场景下,可以使用Nginx作为反向代理服务器,将请求分发到多个SpringBoot应用实例上,实现负载均衡。 通过配置Nginx,可以提高系统的吞吐量和可用性。 同时,Nginx也可以作为统一的入口,对请求进行统一的安全验证和权限控制,例如通过Nginx的
auth_request模块来验证JWT。
总结
SpringBoot 12 结合 JWT 提供了一种简单而强大的身份验证和授权机制。通过本文的介绍,你应该能够掌握如何在SpringBoot项目中集成JWT,并了解一些实战中的避坑经验。在实际项目中,还需要根据具体的业务需求和安全要求,对JWT进行定制化的配置和扩展,以构建更加安全可靠的RESTful API认证体系。
冠军资讯
代码一只喵