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

Spring Security中Delete请求返回403 Forbidden的问题排查与解决

问题描述

我开发了一个Web应用,相关依赖、控制器及安全配置代码如下,目前@GetMapping接口可正常调用,但Postman调用@DeleteMapping接口时返回403 Forbidden,求排查配置错误及解决方法。

pom.xml 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.1.4</version>
</dependency>
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>6.1.4</version>
</dependency>

控制器代码

@RequestMapping("/passports")
@RequiredArgsConstructor
public class PassportController {

    @Operation(
        description = "Удаление паспорта из таблицы",
        summary = "delete passport",
        hidden = false
    )
    @ApiResponse(responseCode = "200", description = "PassportResponse")
    @DeleteMapping("/{id}")
    public void deleteById(
            @PathVariable(name = "id") final long id
    ) throws Exception {
        passportComponent.deleteById(id);
    }
}

安全配置类代码

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfigurer {

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withUsername("user")
                .password(encoder().encode("userPass"))
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();

        requestHandler.setCsrfRequestAttributeName(null);

        http
                .cors(Customizer.withDefaults())
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers(HttpMethod.GET).permitAll()
                        .requestMatchers(HttpMethod.DELETE).permitAll()
                        .anyRequest().authenticated()
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRequestHandler(requestHandler)
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(new HttpSessionCsrfTokenRepository())
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
                )
                .csrf((csrf) -> csrf
                        .ignoringRequestMatchers("/no-csrf"))
                .csrf(Customizer.withDefaults()).csrf((csrf) -> csrf
                        .csrfTokenRepository(new HttpSessionCsrfTokenRepository()))
                .csrf((csrf) -> csrf
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                );
        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        List<UserDetails> userDetailsList = new ArrayList<>();
        userDetailsList.add(User.withUsername("user").password(encoder().encode("userPass"))
                .roles("EMPLOYEE", "USER").build());
        return new InMemoryUserDetailsManager(userDetailsList);
    }
    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

问题分析与解决

错误原因

  1. CSRF配置冲突:你一开始禁用了CSRF,但后续多次重复配置CSRF规则,最后还调用了.csrf(Customizer.withDefaults()),这会重新启用CSRF保护,导致DELETE请求因缺少CSRF令牌被拦截返回403。
  2. 重复用户Bean:配置中定义了两个InMemoryUserDetailsManager类型的Bean,Spring容器会出现Bean歧义,可能引发认证相关问题(不过当前403主要由CSRF导致)。

修正方案

步骤1:清理CSRF配置

保留明确的CSRF规则,删除重复配置。如果是测试环境可以直接禁用CSRF;生产环境建议针对特定接口忽略CSRF。

步骤2:移除重复的用户Bean

只保留一个InMemoryUserDetailsManager Bean。

修正后的安全配置代码

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfigurer {

    // 只保留一个用户信息Bean
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        List<UserDetails> userDetailsList = new ArrayList<>();
        userDetailsList.add(User.withUsername("user")
                .password(encoder().encode("userPass"))
                .roles("EMPLOYEE", "USER")
                .build());
        return new InMemoryUserDetailsManager(userDetailsList);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(Customizer.withDefaults())
                // 测试环境直接禁用CSRF;生产环境替换为下方的忽略规则
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers(HttpMethod.GET).permitAll()
                        .requestMatchers(HttpMethod.DELETE).permitAll()
                        .anyRequest().authenticated()
                );
        
        // 生产环境建议使用以下CSRF配置,只忽略指定接口
        /*
        http
                .cors(Customizer.withDefaults())
                .csrf(csrf -> csrf
                        .ignoringRequestMatchers("/passports/**")
                )
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers(HttpMethod.GET).permitAll()
                        .requestMatchers(HttpMethod.DELETE).permitAll()
                        .anyRequest().authenticated()
                );
        */
        return http.build();
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

验证

修正后重新启动应用,在Postman中调用DELETE /passports/{id}接口,即可正常返回200状态码。


内容的提问来源于stack exchange,提问作者Землянский Григорий

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 02:45:07