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

多WebSecurityConfigurerAdapters实现不同Realm的Spring Security配置问题

问题分析与解决方案

你遇到的问题核心在于Spring Security默认会共享同一个HttpSession中的SecurityContext。当你登录"ws realm"后,认证信息被存储在会话的默认Key下,当你访问/stats时,系统会优先读取这个已有的认证信息——但该认证不具备STATS权限,因此直接返回403,而不会触发"stats realm"的Basic认证流程。

要实现两个Realm完全独立、互不干扰的登录体验,我们需要让每个Security配置使用独立的SecurityContext存储,具体步骤如下:

1. 为每个Security配置自定义SecurityContextRepository

我们可以通过重写HttpSessionSecurityContextRepository的getSessionKey方法,为不同的Realm指定不同的会话存储Key,这样两个认证信息就会被分开存储,不会互相覆盖或读取。

修改后的完整配置代码:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class PortalServiceSecurityConfig {
    @Configuration
    @Order(1)
    static class StatsWebSecConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    .withUser("user").password("{noop}password").authorities("STATS");
            // 注:Spring Security 5+强制要求密码编码器,{noop}仅用于测试,生产环境请替换为BCryptPasswordEncoder等安全实现
        }

        @Bean
        public AuthenticationEntryPoint authenticationEntryPoint() {
            final BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
            entryPoint.setRealmName("stats realm");
            return entryPoint;
        }

        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http.antMatcher("/stats")
                    .authorizeRequests()
                    .antMatchers("/stats").hasAuthority("STATS")
                    .and()
                    .httpBasic().authenticationEntryPoint(authenticationEntryPoint())
                    // 配置stats realm专属的SecurityContext存储
                    .and()
                    .securityContext()
                    .securityContextRepository(new HttpSessionSecurityContextRepository() {
                        @Override
                        protected String getSessionKey(HttpServletRequest request) {
                            return "SPRING_SECURITY_CONTEXT_STATS";
                        }
                    });
        }
    }

    @Configuration
    @Order(2)
    static class PortalServiceConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private WebserviceAuthenticationConfiguration configService;
        @Autowired
        private SoapFaultDetailtConverter soapFaulDetailtConverter;

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Bean
        @Override
        protected UserDetailsService userDetailsService() {
            return new DefaultPortalSecurityService(configService);
        }

        @Bean
        SecurityWebApplicationInitializer securityWebApplicationInitializer() {
            return new SecurityWebApplicationInitializer();
        }

        @Override
        public void configure(final AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService());
        }

        @Bean
        public AuthenticationEntryPoint authenticationEntryPoint() {
            final BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
            entryPoint.setRealmName("ws realm");
            return entryPoint;
        }

        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/resources/**").permitAll()
                    .anyRequest().hasAuthority("WEBSERVICE")
                    .and()
                    .csrf().disable()
                    .httpBasic().authenticationEntryPoint(authenticationEntryPoint())
                    // 配置ws realm专属的SecurityContext存储
                    .and()
                    .securityContext()
                    .securityContextRepository(new HttpSessionSecurityContextRepository() {
                        @Override
                        protected String getSessionKey(HttpServletRequest request) {
                            return "SPRING_SECURITY_CONTEXT_WS";
                        }
                    });
        }
    }
}

2. 额外注意事项

  • 密码编码器:Spring Security 5及以上版本强制要求密码编码器,我在Stats配置中添加了{noop}前缀(仅用于测试场景),如果你的DefaultPortalSecurityService已经处理了密码编码逻辑,可以忽略这部分。
  • 浏览器缓存:Basic认证的凭证会被浏览器缓存,测试时建议使用隐私窗口,或者在每次测试前清除浏览器的缓存/凭证,避免浏览器自动发送之前的认证信息干扰测试结果。
  • @Order顺序:你的配置中StatsWebSecConfig的@Order(1)优先级高于PortalServiceConfig的@Order(2),这个顺序是正确的——Spring Security会优先匹配优先级高的配置,确保/stats请求被第一个配置专属处理。

这样配置后,访问/时会触发"ws realm"的登录,登录成功后访问/stats时,系统会检查自己专属的SecurityContext(此时为空),从而触发"stats realm"的Basic登录表单,两个认证流程完全独立,不会互相干扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:52:57