多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
相关产品推荐
相关产品推荐

