Spring Security实现管理员与用户独立登录认证的问题修复
问题
使用Spring Security搭建网站,需求如下:
- 未登录时访问
/hi,自动跳转至用户登录页/login - 未登录时访问
/admin,自动跳转至管理员登录页/admin/login
目前已完成大部分配置,但存在问题:管理员登录后访问/hi无需跳转可直接进入页面,需要修复该问题,实现管理员与用户认证完全隔离。
相关配置代码如下:
package com.teenboutique.web.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.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.teenboutique.web.services.CustomerDetailServiceImpl; import com.teenboutique.web.services.EmployeeDetailServiceImpl; @Configuration @EnableWebSecurity public class FormLoginSecurityConfig { @Autowired private EmployeeDetailServiceImpl employeeDetailServiceImpl; @Autowired private CustomerDetailServiceImpl customerDetailServiceImpl; @Bean DaoAuthenticationProvider daoAdminAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(employeeDetailServiceImpl); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } @Bean DaoAuthenticationProvider daoUserAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(customerDetailServiceImpl); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } @Bean static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } //This part will be for apis //API will get the order 1 and push all the rest filters down @Bean @Order(1) SecurityFilterChain filterChainAdmin(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(null) .securityMatcher(AntPathRequestMatcher.antMatcher("/admin/**")) .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/admin/css/**", "/admin/images/**", "/admin/fonts/**", "/admin/js/**", "/admin/scss/**", "/admin/vendors/**").permitAll() .requestMatchers("/uploads/**").permitAll() .requestMatchers("/admin/login").permitAll() .anyRequest().authenticated()) .formLogin((formLogin) -> formLogin .usernameParameter("id") .passwordParameter("pass") .loginPage("/admin/login") .defaultSuccessUrl("/admin").permitAll() .failureUrl("/admin/login?error=true").permitAll()) .logout((logout) -> logout.invalidateHttpSession(true).clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout")).logoutSuccessUrl("/admin/login") .permitAll()); httpSecurity.authenticationProvider(daoAdminAuthenticationProvider()); return httpSecurity.build(); } @Bean @Order(2) SecurityFilterChain filterChainUser(HttpSecurity httpSecurity) throws Exception { httpSecurity.securityMatcher(AntPathRequestMatcher.antMatcher("/**")) .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/main/css/**", "/main/images/**", "/main/js/**", "/main/plugins/**").permitAll() .requestMatchers("/uploads/**").permitAll() .requestMatchers("/" ,"/login").permitAll() .anyRequest().authenticated()) .formLogin((formLogin) -> formLogin .usernameParameter("username") .passwordParameter("password") .loginPage("/login") .defaultSuccessUrl("/").permitAll() .failureUrl("/login?error=true").permitAll()) .logout((logout) -> logout.invalidateHttpSession(true).clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/") .permitAll()); httpSecurity.authenticationProvider(daoUserAuthenticationProvider()); return httpSecurity.build(); } }
解决方案
问题根源在于Spring Security默认全局共享SecurityContext,管理员登录后的认证信息会被用户端过滤器链识别,导致无需二次认证即可访问用户页面。要实现完全隔离,需让管理员和用户的认证会话独立存储,具体可通过为两个过滤器链配置不同的Session Cookie名称+独立的SecurityContextRepository实现。
修改后的配置代码如下:
package com.teenboutique.web.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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.teenboutique.web.services.CustomerDetailServiceImpl; import com.teenboutique.web.services.EmployeeDetailServiceImpl; @Configuration @EnableWebSecurity public class FormLoginSecurityConfig { @Autowired private EmployeeDetailServiceImpl employeeDetailServiceImpl; @Autowired private CustomerDetailServiceImpl customerDetailServiceImpl; @Bean DaoAuthenticationProvider daoAdminAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(employeeDetailServiceImpl); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } @Bean DaoAuthenticationProvider daoUserAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(customerDetailServiceImpl); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } @Bean static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean @Order(1) SecurityFilterChain filterChainAdmin(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(null) .securityMatcher(AntPathRequestMatcher.antMatcher("/admin/**")) // 配置管理员端独立的SecurityContext存储 .securityContext(sc -> sc .securityContextRepository(new HttpSessionSecurityContextRepository()) .requireExplicitSave(true)) // 配置管理员专属Session Cookie .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation().migrateSession() .cookieConfig(cookie -> cookie.name("ADMIN_SESSION_ID"))) .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/admin/css/**", "/admin/images/**", "/admin/fonts/**", "/admin/js/**", "/admin/scss/**", "/admin/vendors/**").permitAll() .requestMatchers("/uploads/**").permitAll() .requestMatchers("/admin/login").permitAll() .anyRequest().authenticated()) .formLogin((formLogin) -> formLogin .usernameParameter("id") .passwordParameter("pass") .loginPage("/admin/login") .defaultSuccessUrl("/admin").permitAll() .failureUrl("/admin/login?error=true").permitAll()) .logout((logout) -> logout.invalidateHttpSession(true).clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout")).logoutSuccessUrl("/admin/login") .permitAll()); httpSecurity.authenticationProvider(daoAdminAuthenticationProvider()); return httpSecurity.build(); } @Bean @Order(2) SecurityFilterChain filterChainUser(HttpSecurity httpSecurity) throws Exception { httpSecurity.securityMatcher(AntPathRequestMatcher.antMatcher("/**")) // 配置用户端独立的SecurityContext存储 .securityContext(sc -> sc .securityContextRepository(new HttpSessionSecurityContextRepository()) .requireExplicitSave(true)) // 配置用户专属Session Cookie .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation().migrateSession() .cookieConfig(cookie -> cookie.name("USER_SESSION_ID"))) .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/main/css/**", "/main/images/**", "/main/js/**", "/main/plugins/**").permitAll() .requestMatchers("/uploads/**").permitAll() .requestMatchers("/" ,"/login").permitAll() .anyRequest().authenticated()) .formLogin((formLogin) -> formLogin .usernameParameter("username") .passwordParameter("password") .loginPage("/login") .defaultSuccessUrl("/").permitAll() .failureUrl("/login?error=true").permitAll()) .logout((logout) -> logout.invalidateHttpSession(true).clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/") .permitAll()); httpSecurity.authenticationProvider(daoUserAuthenticationProvider()); return httpSecurity.build(); } }
关键修改点说明
- 独立SecurityContext存储:为两个过滤器链分别配置
HttpSessionSecurityContextRepository,确保认证信息不会跨链共享。 - 专属Session Cookie:为管理员和用户设置不同的Session Cookie名称(
ADMIN_SESSION_ID和USER_SESSION_ID),彻底隔离两个会话的存储,避免互相识别。
修改后,管理员登录后的认证信息仅对/admin/**路径有效,访问/hi时会触发用户端的认证校验,未登录用户(包括已登录的管理员)会被跳转至用户登录页/login,实现了两种认证的完全隔离。
内容的提问来源于stack exchange,提问作者Rocky Operation
相关产品推荐
相关产品推荐

