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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 17:20:43