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

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();
    }
}

关键修改点说明

  1. 独立SecurityContext存储:为两个过滤器链分别配置HttpSessionSecurityContextRepository,确保认证信息不会跨链共享。
  2. 专属Session Cookie:为管理员和用户设置不同的Session Cookie名称(ADMIN_SESSION_ID和USER_SESSION_ID),彻底隔离两个会话的存储,避免互相识别。

修改后,管理员登录后的认证信息仅对/admin/**路径有效,访问/hi时会触发用户端的认证校验,未登录用户(包括已登录的管理员)会被跳转至用户登录页/login,实现了两种认证的完全隔离。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:54:53