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

使用自定义JWT认证过滤器时Spring Security出现403权限错误

Spring Security自定义JWT过滤器角色权限403问题

问题现象

使用Spring Security结合自定义JWT认证过滤器时,permitAll()配置的公共端点可正常访问,但访问需要特定角色的端点(如/v1/api/authorized/**)时,无论用配置中的hasRole("USER")还是控制器上的@PreAuthorize("hasRole('USER')"),都会返回403 Forbidden错误。JWT验证已通过,且手动检查SecurityContextHolder能看到正确的ROLE_USER、ROLE_ADMIN角色信息。

相关代码

1. Spring Security配置类

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth ->
                auth.requestMatchers("/v1/api/public/**").permitAll()
                    .requestMatchers("/v1/api/authorized/**").hasRole("USER")
                    .anyRequest().authenticated())
            .sessionManagement(sessionManagement ->
                sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

2. 自定义JWT过滤器

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserService userService;

    public JwtAuthenticationFilter(JwtService jwtService, UserService userService) {
        this.jwtService = jwtService;
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        final String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            final String jwt = header.substring(7);

            String username = jwtService.extractUsername(jwt);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                User user = userService.getUserByUsername(username);
                if (jwtService.validateToken(jwt, user)) {
                    CustomUserDetails userDetails = new CustomUserDetails(user);

                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());

                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}

3. CustomUserDetails实现类

public class CustomUserDetails implements UserDetails {

    private final User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_USER"), new SimpleGrantedAuthority("ROLE_ADMIN"));
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getEmail();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public User getUser() {
        return user;
    }
}

已尝试操作

  • 切换使用hasRole("USER")和hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")配置权限,均无效;
  • 手动校验GrantedAuthority和SecurityContextHolder中的角色,确认已正确设置;
  • 在控制器方法上添加@PreAuthorize注解,问题依旧。

期望结果

拥有ROLE_USER或ROLE_ADMIN角色的用户可正常访问/v1/api/authorized/**端点,不再返回403错误。


解决方案

1. 确保AuthenticationToken处于已认证状态

部分Spring Security版本中,仅通过构造方法传入权限无法自动标记token为已认证,需显式设置:

UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
        userDetails, null, userDetails.getAuthorities());
// 显式设置认证状态
authToken.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(authToken);

2. 启用方法级安全(若使用@PreAuthorize)

如果控制器依赖@PreAuthorize注解,需在配置类上添加对应注解开启方法级安全:

@Configuration
@EnableMethodSecurity // Spring Security 6.x版本
// @EnableGlobalMethodSecurity(prePostEnabled = true) // Spring Security 5.x版本
public class SecurityConfig {
    // ... 其他配置
}

3. 调整权限匹配规则

尝试用hasAnyRole替代单一hasRole,覆盖多角色场景:

auth.requestMatchers("/v1/api/authorized/**").hasAnyRole("USER", "ADMIN")

4. 检查异步请求的上下文传播

若端点为异步处理,SecurityContextHolder默认的ThreadLocal存储会在线程切换时丢失上下文,需在启动时设置继承策略:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
        SpringApplication.run(Application.class, args);
    }
}

内容的提问来源于stack exchange,提问作者Muhammad Ali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:24:51