Spring Boot 2:基于过滤器的JWT Spring Security实现返回403而非401
我之前升级Spring Boot 1.5到2.x时,也在Security这块踩过类似的坑,尤其是自定义JWT过滤器的部分。结合你的场景,给你几个尽量少改代码的适配方案,核心是兼容Spring Security 5+的变动:
核心变动点先明确
Spring Boot 2.x对应的Spring Security 5版本,主要在这几个地方影响你的过滤器实现:
- 强制要求配置
PasswordEncoder(1.5默认允许无加密,现在不行了) - 过滤器链的顺序逻辑有调整,自定义过滤器的注册方式需要更精准
- 常用的JWT依赖(比如jjwt)旧版本在2.x下可能过时,需要小版本升级适配
逐步调整方案
1. 调整自定义过滤器的注册逻辑
原来你直接addFilter的方式,在2.x里建议替换成addFilterAt,确保你的自定义过滤器替换掉默认的UsernamePasswordAuthenticationFilter,避免顺序问题导致登录请求不被拦截:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JWTUtil jwtUtil; private final UserDetailsService userDetailsService; public SecurityConfig(JWTUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() // 用addFilterAt替换原来的addFilter,指定替换默认的登录过滤器 .and() .addFilterAt(new JWTAuthenticationFilter(authenticationManager(), jwtUtil), UsernamePasswordAuthenticationFilter.class); } // 必须添加PasswordEncoder的Bean,Spring Security 5强制要求 @Bean public PasswordEncoder passwordEncoder() { // 如果要兼容旧的明文密码,临时用NoOp(不推荐生产用),建议改成BCrypt // return NoOpPasswordEncoder.getInstance(); return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }
2. 微调JWTAuthenticationFilter的代码
你的过滤器核心逻辑基本不用改,只需要确保构造方法里明确设置登录端点(和原来保持一致),避免Spring Security默认的"/login"路径冲突:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final JWTUtil jwtUtil; public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) { this.authenticationManager = authenticationManager; this.jwtUtil = jwtUtil; // 显式设置登录处理端点,和你原来的逻辑保持一致 setFilterProcessesUrl("/login"); } // 原来的attemptAuthentication逻辑完全保留 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { // 这里换成你原来的凭证解析逻辑 LoginCredentials credentials = new ObjectMapper().readValue(request.getInputStream(), LoginCredentials.class); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( credentials.getUsername(), credentials.getPassword(), Collections.emptyList()); return authenticationManager.authenticate(authToken); } catch (IOException e) { throw new RuntimeException(e); } } // successfulAuthentication逻辑也完全保留,只需要确保响应头设置正确 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { UserDetails user = (UserDetails) authResult.getPrincipal(); String token = jwtUtil.generateToken(user.getUsername()); response.addHeader("Authorization", "Bearer " + token); } }
3. 升级JWT依赖并微调JWTUtil
如果你的项目用的是jjwt旧版本(比如0.9.1),在Spring Boot 2.x下会有很多过时方法,建议升级到0.11.5版本,只需要微调几个核心方法:
pom.xml依赖调整
<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>
JWTUtil核心方法调整
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import java.nio.charset.StandardCharsets; public class JWTUtil { private static final String SECRET = "your-secret-key-here"; private static final long EXPIRATION_TIME = 86400000; // 1天 public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 替换原来的signWith方法,用Keys生成密钥 .signWith(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody() .getSubject(); } }
总结
这些改动都是最小化的:核心的JWT验证、登录逻辑完全保留,只调整了Spring Security的配置方式和依赖适配,基本不会影响你的业务代码。如果还有其他细节问题,比如权限拦截的过滤器(JWTAuthorizationFilter),调整方式类似,只需要确保注册顺序正确即可。
内容的提问来源于stack exchange,提问作者Nelio Alves
相关产品推荐
相关产品推荐

