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(); } }
问题分析与解决
错误原因
- CSRF配置冲突:你一开始禁用了CSRF,但后续多次重复配置CSRF规则,最后还调用了
.csrf(Customizer.withDefaults()),这会重新启用CSRF保护,导致DELETE请求因缺少CSRF令牌被拦截返回403。 - 重复用户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,提问作者Землянский Григорий
相关产品推荐
相关产品推荐

