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

SpringBoot中使用SecurityFilterChain出现403禁止访问错误

Spring Security权限配置导致ADMIN端点返回403问题排查

问题说明

使用版本:Spring Boot 2.7.8、Spring Security core 6.1.0、spring-security-config 5.7.6、spring-security-web 5.7.6。当前SecurityFilterChain运行异常:配置permitAll()的GET请求可正常访问,但配置hasAuthority("ADMIN")的/admin系列端点返回Forbidden错误。

相关代码

SecurityConfig类

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final JWTAuthFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;
    private final LogoutHandler logoutHandler;

    private static final String AUTH_END_POINT = "/auth/**";
    private static final String CATEGORY_ADMIN = "/categories/admin/**";
    private static final String ORDER_ADMIN = "/orders/admin/**";
    private static final String VENDOR_ADMIN = "/vendors/admin/**";
    private static final String PRODUCT_ADMIN = "/products/admin/**";
    private static final String USER_ADMIN = "/users/admin/**";
    private static final String ADMIN = "ADMIN";

    @Bean
    public SecurityFilterChain securityFilterChain(@NotNull HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers(AUTH_END_POINT).permitAll()
                .antMatchers(HttpMethod.GET, "/vendors/**").permitAll()
                .antMatchers(HttpMethod.GET, "/categories/**").permitAll()
                .antMatchers(HttpMethod.GET, "/products/**").permitAll()
                .antMatchers(HttpMethod.GET, "/users/**").permitAll()
                .antMatchers(HttpMethod.GET, "/orders/**").permitAll()
                .antMatchers(PRODUCT_ADMIN).hasAuthority(ADMIN)
                .antMatchers(VENDOR_ADMIN).hasAuthority(ADMIN)
                .antMatchers(USER_ADMIN).hasAuthority(ADMIN)
                .antMatchers(CATEGORY_ADMIN).hasAuthority(ADMIN)
                .antMatchers(ORDER_ADMIN).hasAuthority(ADMIN)
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .logout()
                .logoutUrl("/auth/logout")
                .addLogoutHandler(logoutHandler)
                .logoutSuccessHandler((request, response, authentication) ->
                        SecurityContextHolder.clearContext());

        return http.build();
    }
}

JWTAuthFilter类的doFilterInternal方法

@Override
protected void doFilterInternal(
        @NonNull HttpServletRequest request,
        @NonNull HttpServletResponse response,
        @NonNull FilterChain filterChain
) throws ServletException, IOException {
    final String authHeader = request.getHeader("Authorization");
    final String jwt;
    final String userEmail;
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        filterChain.doFilter(request, response);
        return;
    }
    jwt = authHeader.substring(7);
    userEmail = jwtTokenProvider.extractUsername(jwt);
    if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
        var isTokenValid = tokenRepository.findByToken(jwt)
                .map(t -> !t.isExpired() && !t.isRevoked())
                .orElse(false);
        if (jwtTokenProvider.isTokenValid(jwt, userDetails) && isTokenValid) {
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
            );
            authToken.setDetails(
                    new WebAuthenticationDetailsSource().buildDetails(request)
            );
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }
    }
    filterChain.doFilter(request, response);
}

排查与解决方向

  1. 权限名称精确匹配检查

    • 确认UserDetails实现类返回的权限集合中,是否包含精确的ADMIN字符串,而非带ROLE_前缀的ROLE_ADMIN。hasAuthority()是精确匹配,若实际权限是ROLE_ADMIN,则需要改为hasAuthority("ROLE_ADMIN"),或者改用hasRole("ADMIN")(该方法会自动添加ROLE_前缀)。
    • 检查JWT令牌中存储的权限字段是否正确,确保jwtTokenProvider解析出的权限与UserDetails中的一致。
  2. 统一Spring Security版本

    • 当前混用了Spring Security 6.1.0的core包和5.7.6的config、web包,版本不一致可能导致权限校验逻辑异常。建议将spring-security-core版本改为5.7.6,与Spring Boot 2.7.x的依赖版本保持一致。
  3. 调整请求匹配规则顺序

    • 当前配置中,宽泛的/users/**等GET请求规则排在/admin规则前面,会导致GET /users/admin/xxx这类请求被直接permitAll放行,但非GET请求的/admin端点会触发权限校验。若你遇到的是GET请求/admin端点返回403,可忽略此点;若为其他方法,需确保权限配置正确。如需让/admin规则优先匹配,可将/admin相关的antMatchers移到前面的permitAll规则之前。
  4. 验证JWT过滤器中的权限加载

    • 在JWTAuthFilter中添加日志,打印userDetails.getAuthorities()的内容,确认当前用户是否真的拥有ADMIN权限。例如:
      System.out.println("当前用户权限:" + userDetails.getAuthorities());
      
    • 同时检查tokenRepository中该JWT令牌是否存在、未过期且未被撤销,以及jwtTokenProvider.isTokenValid()方法是否正确验证了令牌有效性。
  5. 检查AuthenticationProvider配置

    • 确认authenticationProvider Bean是否正确配置,比如DaoAuthenticationProvider是否正确关联了UserDetailsService和PasswordEncoder,确保认证过程中能正确加载用户的权限信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 02:59:57