Spring Boot Security PreAuthenticated 场景,匿名访问

Posted

技术标签:

【中文标题】Spring Boot Security PreAuthenticated 场景,匿名访问【英文标题】:Spring Boot Security PreAuthenticated Scenario with Anonymous access 【发布时间】:2018-08-08 18:06:03 【问题描述】:

我有一个使用 Spring Security 的“pre-authenticated”身份验证方案 (SiteMinder) 的 Spring Boot (1.5.6) 应用程序。

我需要匿名公开执行器“健康”端点,这意味着对该端点的请求将通过 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 端点,但无济于事。

我尝试过的事情:

为匿名访问而不是 permitAll 配置健康端点:

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

将 WebSecurity 配置为忽略健康端点:

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

关闭所有执行器端点的安全性(不知道,但我正在抓住稻草):

management.security.enabled=false

查看日志,问题似乎是 RequestHeaderAuthenticationFilter 被注册为 top level 过滤器,而不是现有 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 或 addFilter 之后变体。也许我应该是?有人知道明确插入过滤器的正确位置吗? (我认为在 Spring Security 的早期版本中删除了显式指定过滤器顺序的需要)

我知道我可以配置RequestHeaderAuthenticationFilter,这样如果标头不存在,它就不会引发异常,但我想尽可能保持这种状态。

我发现这个 SO 帖子似乎与我的问题相似,但不幸的是那里也没有答案。 spring-boot-security-preauthentication-with-permitted-resources-still-authenti

任何帮助将不胜感激! 谢谢!

【问题讨论】:

Filter invoke twice when register as Spring bean的可能重复 【参考方案1】:

问题确实是 RequestHeaderAuthenticationFilter 被注册为 both 作为***过滤器(不需要)以及在 Spring Security FilterChain 中(需要)。

“双重注册”的原因是因为 Spring Boot 会自动向 Servlet Container 注册任何 Filter Bean。

为了防止“自动注册”,我只需要像这样定义一个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/#howto-disable-registration-of-a-servlet-or-filter

另一种/更简单的解决方案: 只是不要将RequestHeaderAuthenticationFilter 类标记为@Bean,这很好,因为该特定过滤器不需要任何类型的DI。通过不使用@Bean 标记过滤器,Boot 不会尝试自动注册它,从而无需定义 FilterRegistrationBean。

【讨论】:

【参考方案2】:

所提供的答案很可能并不完整,因为 Sean Casey 进行了如此多的试验和错误更改,以至于忘记了实际解决问题的配置。我发布了我认为具有正确配置的发现:

如果您错误地将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();

Sean Casey 尝试但最初失败的神奇配置(由于 auth 过滤器的双重注册)是 WebSecurity 配置:

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

请注意,在这种情况下添加 HttpSecurity antMatchers 没有任何作用,似乎 WebSecurity 是优先处理并控制所发生的事情。

额外:根据 Spring Security 文档,WebSecurity.ignore 方法应该用于静态资源,而不是用于应该映射以允许所有用户的动态资源。但是在这种情况下,映射似乎被 PreAuthentication 过滤器覆盖,这会强制使用更激进的ignore 方案。

【讨论】:

以上是关于Spring Boot Security PreAuthenticated 场景,匿名访问的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot Security

Understand Spring Security Architecture and implement Spring Boot Security

如何使用 Spring-Boot 播种 Spring-Security

Spring Boot整合Spring Security总结

Spring Boot:整合Spring Security

spring boot 整合spring security中spring security版本升级的遇到的坑