Spring Boot从2.7.6升级到3.0.1后登录POST请求报错405
问题:Spring Boot 3.0.1 登录POST请求触发405方法不允许异常
将Spring Boot从2.7.6版本升级到3.0.1版本后,登录页面可正常显示,但输入凭证点击提交时,收到错误提示:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported]
同时Tomcat返回HTTP Status 405 – Method Not Allowed状态报告。
相关代码
WebSecurityConfig.java
@Configuration @EnableWebSecurity public class WebSecurityConfig { private final CustomUserDetailsService customUserDetailsService; private final CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler; public WebSecurityConfig(CustomUserDetailsService customUserDetailsService, CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler) { this.customUserDetailsService = customUserDetailsService; this.customizeAuthenticationSuccessHandler = customizeAuthenticationSuccessHandler; } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(customUserDetailsService); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AccessDeniedHandler accessDeniedHandler(){ return new CustomAccessDeniedHandler(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests() .requestMatchers("/").permitAll() .requestMatchers("/login").permitAll() .authenticated() .and() .csrf().disable() .formLogin() .successHandler(customizeAuthenticationSuccessHandler) .loginPage("/login") .failureUrl("/login?error=true") .usernameParameter("email") .passwordParameter("password") .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .invalidateHttpSession(true) .logoutSuccessUrl("/login?logout=true") .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) .and() .authenticationProvider(authenticationProvider()); http .sessionManagement() .maximumSessions(1) .expiredUrl("/login?expired=true"); return http.build(); } // This second filter chain will secure the static resources without reading the SecurityContext from the session. @Bean @Order(0) SecurityFilterChain resources(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**").permitAll() .anyRequest().permitAll()) .requestCache().disable() .securityContext().disable() .sessionManagement().disable(); return http.build(); } }
CustomUserDetailsService.java
@Service public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserByEmail(String email) { System.out.println(email); User user = userRepository.findByEmail(email.toLowerCase()); System.out.println(user.getEmail()); return userRepository.findByEmail(email.toLowerCase()); } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email.toLowerCase()); if (user != null) { List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));; return buildUserForAuthentication(user, authorities); } else { throw new UsernameNotFoundException("username not found"); } } private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) { return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities); } }
问题原因与修复方案
原因
Spring Security 6(适配Spring Boot 3)中,@Order(0)的静态资源过滤器链优先级最高,原配置中anyRequest().permitAll()会让该过滤器链处理所有请求,包括/login的POST请求。但该链禁用了session和securityContext,Spring Security的表单登录过滤器无法介入处理,最终导致POST请求不被支持,返回405。
修复
修改静态资源过滤器链的配置,使用securityMatcher限定该链仅处理指定的静态资源路径,避免干扰业务请求:
@Bean @Order(0) SecurityFilterChain resources(HttpSecurity http) throws Exception { http // 仅匹配静态资源路径 .securityMatcher("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**") .authorizeHttpRequests((authorize) -> authorize .anyRequest().permitAll()) .requestCache().disable() .securityContext().disable() .sessionManagement().disable(); return http.build(); }
补充说明
- 原配置中
authorizeHttpRequests内的anyRequest().permitAll()是核心问题,它导致高优先级的过滤器链拦截了所有请求,包括登录的POST请求。 - 使用
securityMatcher后,该过滤器链仅对静态资源生效,登录请求会被后续的filterChain(默认优先级)处理,表单登录逻辑正常执行。
内容的提问来源于stack exchange,提问作者Enrico Morelli
相关产品推荐
相关产品推荐

