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

Spring Boot 3多SecurityFilterChain配置:仅登录接口创建会话

Spring Boot 3.0.1 多SecurityFilterChain配置实现会话精准控制

需求说明

  • 登录API:允许匿名访问,登录成功后通过Spring Session将会话ID持久化到数据库
  • 其他白名单API:允许匿名访问,不创建任何会话,不进行安全校验
  • 剩余所有API:必须携带x-auth-token请求头才能访问

现有问题

当前单SecurityFilterChain已实现基础权限控制,但白名单接口会自动创建以"client"为principal_name的会话ID存入spring_session表,不符合仅登录接口创建会话的需求。

解决方案:拆分两个SecurityFilterChain

通过拆分三个独立的SecurityFilterChain(含默认接口处理链),分别处理登录接口、白名单接口和剩余接口,精准控制会话创建逻辑:

修改后的完整代码

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig {

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private PasswordEncoder passwordEncoder;

    // 登录接口单独提取
    private static final String[] LOGIN_ENDPOINTS = {
            "/api/usermanager/auth/login",
            "/api/usermanager/auth/app-login",
            "/api/usermanager/back-office/login"
    };

    // 排除登录接口后的白名单
    private static final String[] OTHER_WHITELIST = {
            "/api/usermanager/auth/resetPassword",
            "/api/usermanager/auth/health",
            "/actuator/**",
            "/get-user-names",
            "/get-users",
            "/get-user",
            "/api/usermanager/users/activate",
            "/health/**",
            "/api/usermanager/org",
            "/api/usermanager/org/*/theme",
            "/api/usermanager/image/org/*/all",
            "/api/usermanager/image/org/*/logo.png"
    };

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        var daoAC = new DaoAuthenticationConfigurer(userDetailsService);
        daoAC.passwordEncoder(passwordEncoder);
        var builder = http.getSharedObject(AuthenticationManagerBuilder.class);
        builder.apply(daoAC);
        return builder.build();
    }

    // 第一个过滤器链:仅处理登录接口,负责创建会话
    @Bean
    @Order(1) // 优先级更高,先匹配登录接口
    public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher(LOGIN_ENDPOINTS) // 仅对登录接口生效
                .csrf().disable()
                .cors()
                .and()
                .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .authorizeHttpRequests(requests -> requests.anyRequest().permitAll())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 登录时创建会话
                        .sessionFixation().migrateSession()
                        .maximumSessions(1)
                        .expiredUrl("/sessionExpired.html")
                        .maxSessionsPreventsLogin(false));

        return http.build();
    }

    // 第二个过滤器链:处理白名单接口,不创建会话
    @Bean
    @Order(2)
    public SecurityFilterChain whitelistFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher(OTHER_WHITELIST) // 仅对白名单接口生效
                .csrf().disable()
                .cors()
                .and()
                .authorizeHttpRequests(requests -> requests.anyRequest().permitAll())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER)); // 完全不创建会话

        return http.build();
    }

    // 第三个过滤器链:处理剩余所有API,需要携带x-auth-token
    @Bean
    @Order(3)
    public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/api/**") // 匹配所有API接口(排除已被前两个链处理的)
                .csrf().disable()
                .cors()
                .and()
                .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
                .httpBasic(withDefaults())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.NEVER) // 不创建新会话,仅验证已有会话
                        .sessionFixation().migrateSession());

        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers(HttpMethod.GET,
                "/docs/**", "/resources/**", "/static/**", "/img/**");
    }

    @Bean
    public AuthenticationFailureHandler myFailureHandler() {
        return new CustomAuthenticationFailureHandler();
    }

    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
        return HeaderHttpSessionIdResolver.xAuthToken(); // 使用x-auth-token作为会话ID头
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

关键配置说明

  1. 过滤器链优先级:通过@Order注解指定执行顺序,数字越小优先级越高,确保登录接口和白名单接口先被匹配,避免被默认链拦截。
  2. SecurityMatcher:每个过滤器链通过securityMatcher指定生效的接口路径,实现精准路由。
  3. 会话策略配置:
    • 登录接口链:使用SessionCreationPolicy.IF_REQUIRED,仅在登录成功时创建会话并持久化到数据库。
    • 白名单接口链:使用SessionCreationPolicy.NEVER,完全禁止创建会话,不会在spring_session表中生成记录。
    • 默认接口链:使用SessionCreationPolicy.NEVER,仅验证请求头中的x-auth-token对应的会话,不创建新会话。
  4. 拆分白名单:将登录接口从原AUTH_WHITELIST中单独提取,确保两类接口被不同链处理。

内容的提问来源于stack exchange,提问作者Shehan Simen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:11:05