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

Spring Security所有请求返回403 Forbidden问题排查求助

Spring Security JWT认证配置问题

所有请求均返回403 Forbidden响应。移除@Configuration和@EnableWebSecurity注解后能看到localhost:8080默认登录页,但启用配置后无法访问;禁用CSRF也未解决问题。此外,自定义过滤器链配置异常:希望JWTGeneratorFilter在JWTAuthenticationFilter之前执行,且两者都在BasicAuthenticationFilter之前,但配置后抛出异常。

相关代码

SecurityConfiguration

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    private final SecurityUtil securityUtil;

    public SecurityConfiguration(SecurityUtil securityUtil) {
        this.securityUtil = securityUtil;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
                    @Override
                    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                        CorsConfiguration config = new CorsConfiguration();
                        config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
                        config.setAllowedMethods(Collections.singletonList("*"));
                        config.setAllowCredentials(true);
                        config.setAllowedHeaders(Collections.singletonList("*"));
                        config.setExposedHeaders(Arrays.asList("Authorization"));
                        config.setMaxAge(3600L);
                        return config;
                    }
                })).csrf().disable()
                .addFilterAfter(new JWTGeneratorFilter(securityUtil), BasicAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(), BasicAuthenticationFilter.class)
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers(HttpMethod.POST, "/login").permitAll()
                        .anyRequest().authenticated())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();
    }

    @Bean
    public AuthenticationProvider authenticationProvider(){
        CustomAuthenticationProvider authenticationProvider = new CustomAuthenticationProvider();
        return authenticationProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

JWTGeneratorFilter

@RequiredArgsConstructor
@Component
public class JWTGeneratorFilter extends OncePerRequestFilter {

    private final SecurityUtil securityUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (null == authentication) {
            ObjectMapper mapper = new ObjectMapper();
            CredentialsDTO credentialsDTO = mapper.readValue(request.getInputStream(), CredentialsDTO.class);
            User user = securityUtil.findByCredentialsDTO(credentialsDTO);
            SecretKey key = Keys.hmacShaKeyFor(ConstantStore.JWT_SECRET.getBytes(StandardCharsets.UTF_8));
            String jwt = Jwts.builder().setIssuer("FPE-Weblap").setSubject("JWT")
                    .claim("username", user.getUsername())
                    .claim("authorities", populateAuthorities(user.getRoles()))
                    .setIssuedAt(new Date())
                    .setExpiration(new Date((new Date()).getTime() + 30000000))
                    .signWith(key).compact();
            response.setHeader(ConstantStore.TOKEN_HEADER, jwt);
            request.setAttribute(ConstantStore.TOKEN_HEADER, jwt);
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getRoles()));
        }

        filterChain.doFilter(request, response);
    }

    private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
        Set<String> authoritiesSet = new HashSet<>();
        for (GrantedAuthority authority : collection) {
            authoritiesSet.add(authority.getAuthority());
        }
        return String.join(",", authoritiesSet);
    }
}

JWTAuthenticationFilter

@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String jwt = response.getHeader(ConstantStore.TOKEN_HEADER);
        if (null != jwt) {
            try {
                SecretKey key = Keys.hmacShaKeyFor(
                        ConstantStore.JWT_SECRET.getBytes(StandardCharsets.UTF_8));

                Claims claims = Jwts.parserBuilder()
                        .setSigningKey(key)
                        .build()
                        .parseClaimsJws(jwt)
                        .getBody();
                String username = String.valueOf(claims.get("username"));
                String authorities = (String) claims.get("authorities");
                Authentication auth = new UsernamePasswordAuthenticationToken(username, null,
                        AuthorityUtils.commaSeparatedStringToAuthorityList(authorities)); 
//Yes, my roles start with ROLE_
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (Exception e) {
                throw new BadCredentialsException("Invalid Token received!");
            }

        }
        filterChain.doFilter(request, response);
    }
}

UserDetailsImpl

public class UserDetailsImpl implements UserDetails {

    private User user;

    public UserDetailsImpl(User user) {
        this.user = user;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new HashSet<>();
        List<Role> roles= user.getRoles();
        for(Role role : roles)
        {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
}

解决建议

  1. 修正过滤器顺序与实例化方式
    当前手动new过滤器实例会跳过Spring依赖注入,且顺序配置错误。改为注入Spring管理的过滤器Bean,调整顺序:

    // 修改SecurityConfiguration构造器,注入两个过滤器
    private final SecurityUtil securityUtil;
    private final JWTGeneratorFilter jwtGeneratorFilter;
    private final JWTAuthenticationFilter jwtAuthenticationFilter;
    
    public SecurityConfiguration(SecurityUtil securityUtil, JWTGeneratorFilter jwtGeneratorFilter, JWTAuthenticationFilter jwtAuthenticationFilter) {
        this.securityUtil = securityUtil;
        this.jwtGeneratorFilter = jwtGeneratorFilter;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }
    
    // 配置过滤器链时调整顺序
    http.addFilterBefore(jwtGeneratorFilter, BasicAuthenticationFilter.class)
        .addFilterAfter(jwtAuthenticationFilter, JWTGeneratorFilter.class);
    
  2. 修复JWTAuthenticationFilter的token获取逻辑
    从请求头而非响应头读取token:

    String jwt = request.getHeader(ConstantStore.TOKEN_HEADER);
    
  3. 限制JWTGeneratorFilter的触发范围
    该过滤器仅需处理POST /login请求,避免干扰其他请求(输入流仅能读取一次):

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return !"/login".equals(request.getRequestURI()) || !HttpMethod.POST.name().equals(request.getMethod());
    }
    
  4. 规范用户认证流程
    不要直接通过securityUtil.findByCredentialsDTO验证用户,改用AuthenticationManager执行认证,适配Spring Security的认证机制:

    // 在JWTGeneratorFilter注入AuthenticationManager
    private final AuthenticationManager authenticationManager;
    
    // 认证逻辑替换为
    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(credentialsDTO.getUsername(), credentialsDTO.getPassword());
    Authentication authentication = authenticationManager.authenticate(authToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    // 再基于authentication生成JWT
    
  5. 修正Authentication对象的创建
    使用UserDetailsImpl构建Authentication,确保权限信息正确:

    UserDetails userDetails = new UserDetailsImpl(user);
    Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(auth);
    
  6. 移除重复的sessionManagement配置
    将两次sessionManagement配置合并为一次,避免冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 21:54:59