Spring Boot应用POST请求持续返回403错误排查求助
Spring Boot应用所有POST请求返回403,无效链接未返回404问题
开发Spring Boot应用时遇到以下问题:
- 发送任意POST请求均返回403响应
- 即使请求无效链接,返回的仍是403而非404
- 当前使用Spring Boot 3.1.3版本,曾降级至2.7.15尝试使用
WebSecurityConfigurerAdapter旧特性,且已关闭CSRF,但问题仍未解决
相关代码文件
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>register</artifactId> <version>0.0.1-SNAPSHOT</version> <name>register</name> <description>Demo project for Spring Boot</description> <properties> <java.version>20</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
SecurityConfig.java
package register; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/api/**").hasAuthority("ROLE_myAuthority") .anyRequest().authenticated() ); // ... return http.build(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((requests) -> requests .requestMatchers("/**").permitAll()) .build(); } /* @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest() .permitAll()); return http.build(); } */ }
EmployeeController.java
package register; //import com.example.RegisterLogin.Dto.EmployeeDTO; //import com.example.RegisterLogin.Dto.LoginDTO; //import com.example.RegisterLogin.Service.EmployeeService; //import com.example.RegisterLogin.response.LoginResponse; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController // @Secured("ROLE_myAuthority") @CrossOrigin @RequestMapping("/api/v1/employee") public class EmployeeController { private EmployeeService employeeService; @PostMapping(path = "/save") public String saveEmployee(@RequestBody EmployeeDTO employeeDTO) { return employeeService.addEmployee(employeeDTO); } }
application.properties
spring.application.name=register server.port=8080 spring.jpa.hibernate.ddl-auto=create spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/dbkms?createDatabaseIfNotExist=true spring.datasource.username=username spring.datasource.password=password #jpa vendor adapter configuration spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect spring.jpa.generate-ddl=true spring.jpa.show-sql=true
问题根源与修复方案
核心问题
SecurityConfig中定义了两个SecurityFilterChain Bean:
- 第一个名为
web的Bean,要求/api/**路径必须拥有ROLE_myAuthority权限,其余所有请求都需要认证 - 第二个名为
securityFilterChain的Bean,意图允许所有请求并关闭CSRF
Spring Security会按Bean的注册顺序匹配过滤器链,第一个web的过滤器链优先级更高,导致所有请求都被它拦截:
- 未认证的请求直接返回403
- 无效链接还没到Spring MVC的请求映射阶段就被Security拦截,因此不会返回404
修复步骤
删除多余的web()方法定义,只保留正确的securityFilterChain Bean,修改后的SecurityConfig.java如下:
package register; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((requests) -> requests .requestMatchers("/**").permitAll()) .build(); } }
额外说明
如果需要对特定路径做权限控制,可在同一个SecurityFilterChain中配置,例如:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((requests) -> requests .requestMatchers("/api/v1/employee/save").permitAll() // 允许该路径无需认证 .requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN") // admin路径需要权限 .anyRequest().authenticated()) // 其余路径需要认证 .build(); }
内容的提问来源于stack exchange,提问作者Ilya
相关产品推荐
相关产品推荐

