Сценарий предварительной аутентификации Spring Boot Security с анонимным доступом

У меня есть приложение Spring Boot (1.5.6), в котором используется сценарий аутентификации " Pre-authenticated" (SiteMinder) из Spring Security.

Мне необходимо анонимно выставить конечную точку "работоспособности" привода, что означает, что запросы к этой конечной точке не будут проходить через SiteMinder, и в результате заголовок SM_USER не будет присутствовать в заголовке HTTP-запроса.

Проблема, с которой я сталкиваюсь, заключается в том, что независимо от того, как я пытаюсь настроить конечную точку "работоспособности", инфраструктура выдает org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException потому что ожидаемый заголовок ("SM_USER") отсутствует, когда запрос не проходит через SiteMinder.

Это была моя оригинальная конфигурация безопасности:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

        http
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests().antMatchers("/cars/**", "/dealers/**")
                .hasAnyRole("CLIENT", "ADMIN")
            .and()
                .authorizeRequests().antMatchers("/health")
                .permitAll()
            .and()
                .authorizeRequests().anyRequest().denyAll()
            .and()
                .addFilter(requestHeaderAuthenticationFilter())
                .csrf().disable();
    }

    @Bean
    public Filter requestHeaderAuthenticationFilter() throws Exception {
        RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(preAuthProvider());
    }

    @Bean
    public AuthenticationProvider preAuthProvider() {
        PreAuthenticatedAuthenticationProvider authManager = new PreAuthenticatedAuthenticationProvider();
        authManager.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService());
        return authManager;
    }

    @Bean
    public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> preAuthUserDetailsService() {
        return new UserDetailsByNameServiceWrapper<>(inMemoryUserDetails());
    }

    @Bean
    public UserDetailsService inMemoryUserDetails() {
        return new InMemoryUserDetailsManager(getUserSource().getUsers());
    }

    @Bean
    public UserHolder getUserHolder() {
        return new UserHolderSpringSecurityImple();
    }

    @Bean
    @ConfigurationProperties
    public UserSource getUserSource() {
        return new UserSource();
    }

Я пытался исключить конечную точку /health несколькими разными способами, но безрезультатно.

Вещи, которые я пробовал:

Настройте конечную точку работоспособности для анонимного доступа, а не allowAll:

http
   .authorizeRequests().antMatchers("/health")
   .anonymous()

Настройте WebSecurity, чтобы игнорировать конечную точку работоспособности:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/health");
}

Отключите защиту для всех конечных точек привода (не идея, но я цеплялся за соломинку):

management.security.enabled=false

Глядя на журналы, кажется, что проблема заключается в том, что RequestHeaderAuthenticationFilter регистрируется как фильтр верхнего уровня, а не как фильтр в существующем securityFilterChain:

.s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'webRequestLoggingFilter' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestHeaderAuthenticationFilter' to: [/*]

Исходя из моего понимания, потому что RequestHeaderAuthenticationFilter продолжается AbstractPreAuthenticatedProcessingFilterфреймворк знает, куда вставить фильтр в цепочке, поэтому я не возиться с вариантами addFilterBefore или addFilterAfter. Может быть, я должен быть? Кто-нибудь знает правильное место, чтобы вставить фильтр явно? (Я думал, что необходимость явно указывать порядок фильтров была удалена в предыдущих версиях Spring Security)

Я знаю, что могу настроить RequestHeaderAuthenticationFilter чтобы он не выдавал исключение, если заголовок отсутствует, но я бы хотел оставить его включенным, если это вообще возможно.

Я нашел этот пост, который, похоже, похож на мою проблему, но, к сожалению, там тоже нет ответа. весна-загрузки безопасности предварительной проверки подлинности-с разрешенными-ресурсами, еще Authenti

Любая помощь будет принята с благодарностью! Спасибо!

2 ответа

Проблема заключалась в том, что RequestHeaderAuthenticationFilter регистрировался как фильтр верхнего уровня (нежелательный), а также внутри Spring Security FilterChain (желательно).

Причина "двойной регистрации" заключается в том, что Spring Boot зарегистрирует любую Filter Фасоль с контейнером сервлетов автоматически.

Чтобы предотвратить "авторегистрацию", мне просто нужно было определить FilterRegistrationBean вот так:

@Bean
public FilterRegistrationBean registration(RequestHeaderAuthenticationFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

Документы: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/

Альтернативное / более простое решение: просто не отмечайте RequestHeaderAuthenticationFilter класс как @Bean это нормально, потому что для этого конкретного фильтра не требуется никакой DI. Не помечая фильтр @Bean, Boot не будет пытаться автоматически зарегистрировать его, что устраняет необходимость в определении FilterRegistrationBean.

Предоставленный ответ не является полным, скорее всего, потому, что Шон Кейси сделал так много проб и ошибок, что потерял представление о том, какая конфигурация действительно устранила проблему. Я публикую свои выводы, которые, как мне кажется, имеют правильную конфигурацию:

Если вы неправильно зарегистрировали RequestHeaderAuthenticationFilterкак @Bean, как говорится в исходном ответе, затем удалите его. Простое создание его как обычного экземпляра и добавление в качестве фильтра регистрирует его правильно:

      @Override
protected void configure(HttpSecurity http) throws Exception {
    RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
    // configure your filter
    http
        .authorizeRequests().anyRequest().authenticated()
        .and()
        .addFilter(filter)
        .csrf().disable();
}

Волшебная конфигурация, которую попробовал Шон Кейси, но сначала не удалось (из-за двойной регистрации фильтра авторизации), это конфигурация:

      @Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/health");
}

Обратите внимание, что добавление HttpSecurity antMatchersничего не делает в этом случае, кажется, что WebSecurityэто тот, кто имеет приоритет и контролирует то, что происходит.

ДОПОЛНИТЕЛЬНО: согласно документации Spring Security, WebSecurity.ignoreследует использовать для статических ресурсов, а не для динамических, которые вместо этого должны быть сопоставлены, чтобы разрешить всем пользователям. Однако в этом случае кажется, что сопоставление переопределяется фильтром PreAuthentication, который заставляет использовать более агрессивный ignoreсценарий.