You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:55:15