
本文详细讲解如何在 spring security 6 中基于 bearer token 实现 jwt 认证,涵盖依赖配置、用户详情加载、jwt 工具类、过滤器链集成及安全配置类编写,确保无状态、无 session 的 rest api 安全访问。
本文详细讲解如何在 spring security 6 中基于 bearer token 实现 jwt 认证,涵盖依赖配置、用户详情加载、jwt 工具类、过滤器链集成及安全配置类编写,确保无状态、无 session 的 rest api 安全访问。
在 Spring Security 6 中,WebSecurityConfigurerAdapter 已被弃用,取而代之的是函数式风格的 SecurityFilterChain Bean 配置。以下是实现 JWT Token 验证的完整、生产就绪型实践步骤:
✅ 1. 必备依赖(Maven)
确保 pom.xml 包含以下核心依赖:
<dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-security</artifactid></dependency><dependency><groupid>org.springframework.boot</groupid><artifactid>spring-boot-starter-web</artifactid></dependency><!-- JWT 支持 --><dependency><groupid>io.jsonwebtoken</groupid><artifactid>jjwt-api</artifactid><version>0.12.5</version></dependency><dependency><groupid>io.jsonwebtoken</groupid><artifactid>jjwt-impl</artifactid><version>0.12.5</version><scope>runtime</scope></dependency><dependency><groupid>io.jsonwebtoken</groupid><artifactid>jjwt-jackson</artifactid><version>0.12.5</version><scope>runtime</scope></dependency>
⚠️ 注意:使用 jjwt-api + jjwt-impl + jjwt-jackson 组合(替代已废弃的 jjwt-core),并严格匹配版本。
✅ 2. 用户详情服务(UserDetailsService)
用于根据用户名加载用户凭证,通常对接数据库或远程服务:
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository; // 自定义 Repository
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username)
.map(user -> User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 已 BCrypt 加密
.authorities(AuthorityUtils.createAuthorityList("ROLE_USER"))
.build())
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
}
✅ 3. JWT 工具类(JwtTokenUtil)
封装 Token 生成、解析与校验逻辑(推荐使用 Jwts.builder() + HmacSHA256):
@Component
public class JwtTokenUtil {
private static final String SECRET_KEY = "your-32-byte-secret-key-here!"; // 生产环境请从配置中心/环境变量注入
private static final long EXPIRATION_MS = 86400_000L; // 24 小时
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseSignedClaims(token);
return true;
} catch (Exception e) {
return false;
}
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
}
✅ 提示:Keys.hmacShaKeyFor(...) 是 JWT 0.12+ 推荐的安全密钥构造方式,替代过时的 setSigningKey()。
✅ 4. JWT 认证过滤器(JwtAuthenticationFilter)
继承 OncePerRequestFilter,提取 Authorization: Bearer
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwt);
} catch (Exception e) {
// Token 解析失败,跳过认证
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
✅ 5. 安全配置主类(SecurityConfig)
使用 @Bean SecurityFilterChain 替代旧版 configure() 方法,关键点:
- 禁用 CSRF(JWT 场景下无需)
- 设置 STATELESS 会话策略
- 必须显式注册 JWT 过滤器(addFilterBefore)
- 显式配置 AuthenticationManager 和 PasswordEncoder
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsServiceImpl userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // JWT 不依赖 Cookie,禁用 CSRF
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 强制无状态
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll() // 登录接口放行
.requestMatchers("/actuator/**").permitAll() // 健康检查放行
.anyRequest().authenticated()); // 其他请求需认证
// 注册 JWT 过滤器,在 UsernamePasswordAuthenticationFilter 之前执行
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
HttpSecurity http,
PasswordEncoder passwordEncoder,
UserDetailsServiceImpl userDetailsService) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(provider);
}
}
✅ 补充说明与最佳实践
- ? 密钥安全:SECRET_KEY 绝不可硬编码,应通过 @Value("${jwt.secret}") 从 application.yml 或 Vault 加载。
- ? 测试建议:为 JwtAuthenticationFilter 编写单元测试(Mock HttpServletRequest),验证 Token 提取与上下文设置逻辑。
- ?️ 增强防护:可在过滤器中增加黑名单(Redis 存储已注销 Token)、签发时间校验(nbf, iat)、IP 绑定等。
- ? 路径匹配优化:使用 requestMatchers()(Spring Security 6+)替代已废弃的 antMatchers()。
完成以上配置后,所有 /api/** 下受保护端点将自动校验 Authorization: Bearer











