使用自定义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
相关产品推荐
相关产品推荐

