Spring Безопасность - несколько конфигураций - добавление LogoutHandler

У меня есть приложение spring -boot с использованием spring -security. Конфигурация безопасности разделена на несколько экземпляров 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 обеих конфигураций не изменяет результат.

Что не хватает в моей конфигурации?

Ответ 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;
}

Если вам действительно нужна модульная конфигурация безопасности, я бы предложил создать отдельную конфигурацию безопасности для выхода из системы и других сфер. Вы можете определить обработчиков выходов как beans (используя аннотацию @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!");
        };
    }
}

Ответ 2

Вы должны решить эту проблему с CompositeLogoutHandler в своей единственной конечной точке операции /logout.

Вы по-прежнему можете сохранить два WebSecurityConfigurerAdapter, но вы будете конгломератировать функцию выхода для двух LogoutHandler в одно составное действие:

new CompositeLogoutHandler(loggedOutHandler1, loggedOutHandler2);