Spring Security - 多种配置 - 添加 LogoutHandler

Posted

技术标签:

【中文标题】Spring Security - 多种配置 - 添加 LogoutHandler【英文标题】:Spring Security - multiple configurations - add LogoutHandler 【发布时间】:2018-04-14 08:51:12 【问题描述】:

我有一个使用 spring-security 的 spring-boot 应用程序。安全配置被拆分为多个WebSecurityConfigurerAdapter 实例。

我有一个配置注销的地方:

@Override
protected void configure(HttpSecurity http) throws Exception 

    // configure logout
    http
            .logout()
            .logoutUrl("/logout")
            .invalidateHttpSession(true)
            .addLogoutHandler((request, response, authentication) -> 
                System.out.println("logged out 1!");
            )
            .permitAll();

    // ... more security configuration, e.g. login, CSRF, rememberme

还有另一个WebSecurityConfigurerAdapter,我几乎什么都不想做,除了添加另一个LogoutHandler:

@Override
protected void configure(HttpSecurity http) throws Exception 

    // configure logout
    http
            .logout()
            .logoutUrl("/logout")
            .addLogoutHandler((request, response, authentication) -> 
                System.out.println("logged out 2!");
            );

两个configure() 方法都被调用。但是,如果我确实注销,则只会调用第一个 LogoutHandler。更改两种配置的@Order 不会改变结果。

我的配置中缺少什么?

【问题讨论】:

您是在尝试将两个注销处理程序注册到同一个 url,还是要专门设置一个不运行其他所有内容的注销? 两个 LogoutHandlers 应该在同一个注销时运行。 试试那个链接,docs.spring.io/spring-security/site/docs/current/reference/… 我觉得你的网址是一样的。 我当然知道那个链接,但我应该特别关注哪个部分?我的观点是模块化配置功能。 他们有@Order(2) 和@Order(3),改变它不会影响调用哪个处理程序。它始终是更“宽”配置中的一个。无论如何,在这两种情况下,我都希望两者都被调用。但也许当前版本无法做到这一点? 【参考方案1】:

当您创建多个安全配置时,Spring Boot 将为每个配置创建一个单独的 SecurityFilterChain。见WebSecurity:

@Override
protected Filter performBuild() throws Exception 
    // ...
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) 
        securityFilterChains.add(securityFilterChainBuilder.build());
    
    // ...

当应用程序收到注销请求时FilterChainProxy将只返回一个SecurityFilterChain:

private List<Filter> getFilters(HttpServletRequest request) 
    for (SecurityFilterChain chain : filterChains) 
        // Only the first chain that matches logout request will be used:
        if (chain.matches(request)) 
            return chain.getFilters();
        
    

    return null;

如果您真的需要模块化安全配置,我建议为注销和其他领域创建单独的安全配置。您可以在不同的配置类中将注销处理程序定义为 bean(使用 @Bean 注释),并在注销配置中收集这些处理程序:

WebSecurityLogoutConfiguration.java

@Configuration
@Order(99)
public class WebSecurityLogoutConfiguration extends WebSecurityConfigurerAdapter 

    // ALL YOUR LOGOUT HANDLERS WILL BE IN THIS LIST
    @Autowired
    private List<LogoutHandler> logoutHandlers;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        // configure only logout
        http
                .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                // USE CompositeLogoutHandler
                .addLogoutHandler(new CompositeLogoutHandler(logoutHandlers));
        http.csrf().disable(); // for demo purposes
    

WebSecurity1Configuration.java

@Configuration
@Order(101)
public class WebSecurity1Configuration extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        // ... more security configuration, e.g. login, CSRF, rememberme
        http.authorizeRequests()
                .antMatchers("/secured/**")
                .authenticated();
    

    // LOGOUT HANDLER 1
    @Bean
    public LogoutHandler logoutHandler1() 
        return (request, response, authentication) -> 
            System.out.println("logged out 1!");
        ;
    

WebSecurity2Configuration.java

@Configuration
@Order(102)
public class WebSecurity2Configuration extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests()
                .antMatchers("/api/**")
                .permitAll();
    

    // LOGOUT HANDLER 2
    @Bean
    public LogoutHandler logoutHandler2() 
        return (request, response, authentication) -> 
            System.out.println("logged out 2!");
        ;
    

【讨论】:

当我将所有LogoutHandlers 放在一个地方时,我还需要CompositeLogoutHandler 还是不能将它们全部添加到一个循环中?除此之外,您的答案看起来很有希望。【参考方案2】:

您应该在单个 /logout 操作端点上使用 CompositeLogoutHandler 解决此问题。

您仍然可以根据需要保留两个 WebSecurityConfigurerAdapter,但您会将两个 LogoutHandlers 的注销功能合并到一个复合操作中:

new CompositeLogoutHandler(loggedOutHandler1, loggedOutHandler2);

【讨论】:

这是我想避免的那种手动接线(CompositeLogoutHandler 内部不会发生太多魔法)。我想提供一个独立的WebSecurityConfigurerAdapter,它将添加自己工作的功能。最简单的示例是记录每次登录和注销。但似乎addLogoutHandler 的行为更像setLogoutHandler【参考方案3】:

关键是你应该创建独立的 AuthenticationManger 实例。

Here is an sample for multiples WebSecurityAdapter

【讨论】:

以上是关于Spring Security - 多种配置 - 添加 LogoutHandler的主要内容,如果未能解决你的问题,请参考以下文章

springsecurity配置多种token过期规则

Spring Security---ONE

Spring Security自定义登录认证与数据持久化

更改 Spring Security SAML2 登录的 URL

Spring Security登录验证流程源码解析

在运行时延迟初始化 Spring Security + 重新加载 Spring Security 配置