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

Spring Security多登录页配置异常:/user/**未跳转至用户登录页

Spring Boot多登录页面配置问题排查与解决

问题描述

我有一个Spring Boot应用,配置了两个登录页面,已使用@Order注解配置多个Filter Chain,但仅部分生效。访问/**路径可正常放行,访问/admin/**路径会重定向至管理员登录页,但访问/user/**路径不会重定向至用户登录页。

原配置代码

package com.business.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.business.service.impl.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

  @Autowired
  UserDetailsServiceImpl userDetailService;

  @Bean
  public static BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public DaoAuthenticationProvider authProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailService);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
  }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
        throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Configuration
  @Order(1)
  public static class AdminSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain1(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests()
                .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ROLE_ADMIN")
                .and()
            .formLogin()
                .loginPage("/admin-login")
                .permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .defaultSuccessUrl("/admin/home")
                .and()
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .and()
            .exceptionHandling()
                .accessDeniedPage("/403")
                .and()
            .authorizeHttpRequests()
                .requestMatchers(new AntPathRequestMatcher("/**"));
        http
            .csrf().disable()
            .headers()
                .frameOptions().disable();
        return http.build();
    }
  }

  @Configuration
  @Order(2)
  public static class UserSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests()
                .requestMatchers(new AntPathRequestMatcher("/user/**")).hasRole("ROLE_USER")
                .and()
            .formLogin()
                .loginPage("/user-login")
                .permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .defaultSuccessUrl("/user/home")
                .and()
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .and()
            .exceptionHandling()
                .accessDeniedPage("/403")
                .and()
            .authorizeHttpRequests()
                .requestMatchers("/**").permitAll();
        http
            .csrf().disable()
            .headers()
                .frameOptions().disable();
        return http.build();
    }
  }
}

问题原因

  1. FilterChain范围覆盖冲突:第一个AdminSecurityConfig(@Order(1))最后添加了.requestMatchers(new AntPathRequestMatcher("/**")),未明确权限规则但会导致所有请求都被该FilterChain处理,后续的UserSecurityConfig根本不会触发。Spring Security的FilterChain按@Order顺序执行,匹配到第一个符合条件的就会处理请求,不会流转到下一个。
  2. 缺少明确的请求匹配限定:未给每个FilterChain指定securityMatcher,导致Spring Security无法精准判断哪个FilterChain处理哪些请求,依赖授权规则容易出现覆盖问题。

修复方案

  1. 给每个FilterChain添加securityMatcher,明确其负责处理的请求路径范围。
  2. 修正授权规则,确保每个FilterChain只处理自身职责内的路径,避免全局覆盖。
  3. 新增默认FilterChain处理其余所有路径,保证权限规则互不干扰。

修改后的配置代码

package com.business.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.business.service.impl.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

  @Autowired
  UserDetailsServiceImpl userDetailService;

  @Bean
  public static BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public DaoAuthenticationProvider authProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailService);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
  }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
        throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Configuration
  @Order(1)
  public static class AdminSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain1(HttpSecurity http) throws Exception {
        http
            // 明确该FilterChain只处理/admin/**和/admin-login路径
            .securityMatcher("/admin/**", "/admin-login")
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(new AntPathRequestMatcher("/admin-login")).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ROLE_ADMIN")
            )
            .formLogin(form -> form
                .loginPage("/admin-login")
                .permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .defaultSuccessUrl("/admin/home")
            )
            .logout(logout -> logout
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
            )
            .exceptionHandling(ex -> ex
                .accessDeniedPage("/403")
            )
            .csrf(csrf -> csrf.disable())
            .headers(headers -> headers.frameOptions().disable());
            
        return http.build();
    }
  }

  @Configuration
  @Order(2)
  public static class UserSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
        http
            // 明确该FilterChain只处理/user/**和/user-login路径
            .securityMatcher("/user/**", "/user-login")
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(new AntPathRequestMatcher("/user-login")).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/user/**")).hasRole("ROLE_USER")
            )
            .formLogin(form -> form
                .loginPage("/user-login")
                .permitAll()
                .usernameParameter("email")
                .passwordParameter("password")
                .defaultSuccessUrl("/user/home")
            )
            .logout(logout -> logout
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
            )
            .exceptionHandling(ex -> ex
                .accessDeniedPage("/403")
            )
            .csrf(csrf -> csrf.disable())
            .headers(headers -> headers.frameOptions().disable());
            
        return http.build();
    }
  }

  // 新增默认FilterChain,处理其他所有路径(如/**)
  @Configuration
  @Order(3)
  public static class DefaultSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain3(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            )
            .csrf(csrf -> csrf.disable())
            .headers(headers -> headers.frameOptions().disable());
            
        return http.build();
    }
  }
}

说明

  • 通过securityMatcher明确每个FilterChain的处理范围,避免请求被提前拦截。
  • 拆分默认路径的处理到单独的FilterChain,保证/admin/**和/user/**的规则互不干扰。
  • 每个FilterChain内的授权规则更清晰,明确放行登录页面,保护对应权限路径。

内容的提问来源于stack exchange,提问作者Thomas Shelby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 03:12:18