Spring 会话:如何为不同的 URL 创建单独的会话管理策略

Spring Session: How to create separate session management policies for different URLs

在使用spring会话的spring引导项目中,如何为不同的URL配置两个会话管理策略?

  1. 对于 Angular 前端,我想使用默认实现并创建 X-Auth-Token 令牌(如果需要则创建会话)
  2. 但对于公开的 API 端点,我想使用无状态会话管理

我尝试了以下配置,但根本没有创建会话。我认为第二个块正在覆盖 sessionCreationPolicy 因为它在末尾

SecurityConfig.java

@Override
    public void configure(HttpSecurity http) throws Exception
    {


//Requests from angular app
http.authorizeRequests()
        .antMatchers("/", "/login","/api/v1/user/login", "/api/v1/user/authenticate", "/api/v1/user/logout", "/api/v1/health/find/status").permitAll()
        .antMatchers("/api/v1/person/**").hasAnyAuthority(ROLE_USER)
        .and()
        .httpBasic()
        .and()
    .exceptionHandling().authenticationEntryPoint(customBasicAuthenticationEntryPoint)
        .and()
        .logout()
        .invalidateHttpSession(true).clearAuthentication(true)
        .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

//Requests from external systems
http.authorizeRequests()
        .antMatchers("/api/v1/external/**").hasAnyAuthority(ROLE_API_USER)
        .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

}

更新

为 API 端点和 Angular 应用程序添加了自定义 WebSecurity 配置器适配器,如下所示。添加后,API 端点不创建会话但 Angular HTTP 请求也不创建会话

@Configuration
@EnableWebSecurity
public class SecurityConfig
{
 ....

    @Configuration
    @Order(1)
    public class ExternalApiSecurityConfig extends WebSecurityConfigurerAdapter
    {
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth)
        {
            auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).eraseCredentials(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception
        {
            http.authorizeRequests()
                    .antMatchers("/api/v1/search/**").hasAnyAuthority(ROLE_API_USER, ROLE_SYS_ADMIN)
                    .and()
                    .httpBasic()
                    .and()
                    .exceptionHandling().authenticationEntryPoint(customBasicAuthenticationEntryPoint)
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

            http.headers()
                    .frameOptions().disable();

            // Uses CorsConfigurationSource bean defined below
            http.cors().configurationSource(corsConfigurationSource());

            http.csrf().disable();
        }
    }

    @Configuration
    @Order(2)
    public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter
    {
        @Bean(BeanIds.AUTHENTICATION_MANAGER)
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception
        {
            return super.authenticationManagerBean();
        }

        @Override
        public void configure(WebSecurity webSecurity)
        {
            webSecurity.ignoring().antMatchers("/static/**");
        }

        @Override
        public void configure(HttpSecurity http) throws Exception
        {
    http.authorizeRequests()
        .antMatchers("/", "/login","/api/v1/user/login", "/api/v1/user/authenticate", "/api/v1/user/logout", "/api/v1/health/find/status").permitAll()
        .antMatchers("/api/v1/person/**").hasAnyAuthority(ROLE_USER)
        .and()
        .httpBasic()
        .and()
    .exceptionHandling().authenticationEntryPoint(customBasicAuthenticationEntryPoint)
        .and()
        .logout()
        .invalidateHttpSession(true).clearAuthentication(true)
        .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        }

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth)
        {
            auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).eraseCredentials(true);
            auth.authenticationProvider(getDaoAuthenticationProvider()).eraseCredentials(true);
        }

    }
 ....
}

您需要创建两个不同的过滤器链,当您创建一个 WebSecurityConfigurerAdapter 时,您正在创建一个包含所需安全 http 过滤器的代理过滤器链。

如果您在日志中查找 DefaultSecurityFilterChain,您应该能够在启动时看到这一点,您应该再次看到一条类似于 Creating filter chain: any request, [Filters...]

的日志记录语句

默认情况下 DefaultSecurityFilterChain 是任何请求 (/**) 都会通过它。为了能够分离出对不同过滤器链的请求,您需要创建第二个 WebSecurityConfigurerAdapter,将其范围限定到任何路径并将您想要的配置应用到每个过滤器链。

一个问题是您还需要对适配器应用更高的优先级,结束代码类似于。

  @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    @Configuration
    public static class ApiSecurity extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http.antMatcher("/api/v1/external/**")
                    .authorizeRequests()
                    .anyRequest()
                    .hasAnyAuthority(ROLE_API_USER)
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }

    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    @Configuration
    public static class DefaultSecurityFilter extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            //Requests from angular app
            http.authorizeRequests()
                    .antMatchers("/", "/login","/api/v1/user/login", "/api/v1/user/authenticate", "/api/v1/user/logout", "/api/v1/health/find/status").permitAll()
                    .antMatchers("/api/v1/person/**").hasAnyAuthority(ROLE_USER)
                    .and()
                    .httpBasic()
                    .and()
                    .exceptionHandling().authenticationEntryPoint(customBasicAuthenticationEntryPoint)
                    .and()
                    .logout()
                    .invalidateHttpSession(true).clearAuthentication(true)
                    .and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        }
    }

在此之后,您应该会看到在日志中创建了两个 DefaultSecurityFilterChain,第一个是 "/api/v1/external/**",第二个 any request 用于捕获任何其他请求。