【问题标题】:How do I add a filter to spring-security based on a @Profile?如何基于 @Profile 向 spring-security 添加过滤器?
【发布时间】:2018-09-14 05:40:38
【问题描述】:

我正在尝试定义两个不同的 bean(都扩展 AbstractPreAuthenticatedProcessingFilter):一个用于在“开发”配置文件处于活动状态时从请求中获取标头(例如 USER_ID),第二个用于从在“开发”配置文件处于活动状态时请求标头。 (虽然从概念上讲,我真的只是想根据 bean 本身的存在以编程方式注册过滤器)目前,我什至没有尝试使用配置文件,因为我遇到了自动获取标题的问题在适当的过滤器链中注册。

应用使用 Spring-Boot 2.0.0.RELEASE,配置为使用嵌入式 Tomcat,服务被注解为 @RestController。下面是我的 SecurityConfig 类的简化版本:

@Configuration
@EnableWebSecurity(debug=true)
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


@Bean
public UserIdAuthenticationFilter UserIdAuthenticationFilter() throws Exception {
    ...
}

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
    ...
}


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

    http
        .cors().and()
        .csrf().disable()
        .headers().frameOptions().sameOrigin()
        .and()
        //.addFilter(jwtAuthenticationFilter())
        //.addFilter(UserIdAuthenticationFilter())
        .exceptionHandling()
        .and().authorizeRequests()
        .antMatchers("/", "/index.html", "/css/**", "/images/**", "/js/**").permitAll()
        .anyRequest()
        .authenticated()
        .antMatchers("/**").permitAll()
    ;


 }

}

如您所见,我已经定义了我的过滤器 bean,并且它们在应用于正确的链时工作......我看到的问题是 spring 似乎注册到过滤器某处,但是当我调用服务端点时,它从不调用我添加的过滤器代码。

运行应用程序的日志输出似乎表明正在定位过滤器...

 2018-04-04 09:43:02.907  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
 2018-04-04 09:43:02.907  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
 2018-04-04 09:43:02.907  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ost-startStop-1] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpTraceFilter' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'webMvcMetricsFilter' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ LOOK HERE ] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'userIdAuthenticationFilter' to: [/*]
 2018-04-04 09:43:02.908  INFO 7717 --- [ LOOK HERE ] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'jwtAuthenticationFilter' to: [/*]
 2018-04-04 09:43:02.909  INFO 7717 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
 2018-04-04 09:43:02.922 DEBUG 7717 --- [ost-startStop-1] g.n.a.f.w.c.a.f.JwtAuthenticationFilter  : Initializing filter 'jwtAuthenticationFilter'
 2018-04-04 09:43:02.922 DEBUG 7717 --- [ost-startStop-1] g.n.a.f.w.c.a.f.JwtAuthenticationFilter  : Filter 'jwtAuthenticationFilter' configured successfully
 2018-04-04 09:43:02.922 DEBUG 7717 --- [ost-startStop-1] c.a.f.UserIdAuthenticationFilter : Initializing filter 'userIdAuthenticationFilter'
 2018-04-04 09:43:02.922 DEBUG 7717 --- [ost-startStop-1] c.a.f.UserIdAuthenticationFilter : Filter 'userIdAuthenticationFilter' configured successfully

现在应用程序正在运行,当我尝试访问该服务时,我看到 spring 转储以下输出(启用调试):

 Security filter chain: [
   WebAsyncManagerIntegrationFilter
   SecurityContextPersistenceFilter
   HeaderWriterFilter
   CorsFilter
   LogoutFilter
   RequestCacheAwareFilter
   SecurityContextHolderAwareRequestFilter
   AnonymousAuthenticationFilter
   SessionManagementFilter
   ExceptionTranslationFilter
   FilterSecurityInterceptor
   ]

鉴于该输出,它向我表明安全过滤器链确实没有应用了我的过滤器。

也许更能说明问题的是,如果您注意到我的配置代码 sn-p 中有两行注释掉了(这是我在尝试使用基于配置文件的检测之前手动添加过滤器的地方)。如果我取消注释添加 JWT 过滤器的行(也就是手动注册过滤器,而不是依赖检测),一切似乎都按预期工作。查看调试输出,我现在在手动添加过滤器后调用端点时看到以下内容(注意 JwtAuthenticationFilter 现在存在于安全过滤器链中):

 Security filter chain: [
     WebAsyncManagerIntegrationFilter
     SecurityContextPersistenceFilter
     HeaderWriterFilter
     CorsFilter
     LogoutFilter
     JwtAuthenticationFilter <-----
     RequestCacheAwareFilter
     SecurityContextHolderAwareRequestFilter
     AnonymousAuthenticationFilter
     SessionManagementFilter
     ExceptionTranslationFilter
     FilterSecurityInterceptor
   ]

我真的要问两个问题...我显然不了解 spring-boot/spring-security 设置的各种过滤器链,那么在应用程序启动期间注册的 bean 有什么区别在正常的过滤器链中,与在 spring-security 过滤器链中注册的 bean 相比?谁能指出我做错了什么以及为什么?

注册这些“可选”bean 的正确方法是什么? (可选,因为可能不存在依赖于活动配置文件。)

谢谢!

【问题讨论】:

    标签: java spring spring-boot spring-security


    【解决方案1】:

    来自article关于Spring Security架构:

    Spring Security 作为单个过滤器安装在链中,它的具体类型是 FilterChainProxy,原因很快就会显现出来。在 Spring Boot 应用程序中,安全过滤器是 ApplicationContext 中的 @Bean,默认情况下会安装它,以便将其应用于每个请求。

    在同一个顶级FilterChainProxy中可以有多个由Spring Security管理的过滤器链,并且对容器都是未知的。 Spring Security 过滤器包含一个过滤器链列表,并将请求分派到与其匹配的第一个链。

    还要注意:

    Spring Security 内部的所有过滤器对于容器来说都是未知的这一事实很重要,尤其是在 Spring Boot 应用程序中,默认情况下,所有 Filter 类型的 @Beans 都会自动注册到容器中。因此,如果您想将自定义过滤器添加到安全链中,则需要不将其设为 @Bean 或将其包装在明确禁用容器注册的 FilterRegistrationBean 中。

    因此,当您将过滤器定义为 Spring bean 时,它会自动注册到 servlet 容器,但不会注册到 Spring Security 过滤器链。这就是为什么您需要使用 addFilter 方法将其显式添加到 Spring Security 链中的原因。您还需要在 servlet 容器中禁用自动注册,否则过滤器将被调用两次。

    另见:

    至于配置文件,至少有两种方法可以满足您的需求:

    1. 扩展 AbstractHttpConfigurer 并将通用安全配置移到那里。之后为每个配置文件创建单独的安全配置:

      @Configuration
      @EnableWebSecurity(debug = true)
      @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
      public class SecurityConfiguration {
      
          /**
           * Development security configuration.
           */
          @Profile("dev")
          @Configuration
          public static class DevSecurityConfiguration extends WebSecurityConfigurerAdapter {
      
              @Bean
              public FilterRegistrationBean userIdAuthenticationFilter() {
                  // ...
              }
      
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.apply(commonSecurityConfiguration())
                          .and().addFilter(userIdAuthenticationFilter().getFilter());
              }
          }
      
          /**
           * Production security configuration.
           */
          @Profile("!dev")
          @Order(1)
          @Configuration
          public static class ProdSecurityConfiguration extends WebSecurityConfigurerAdapter {
      
              @Bean
              public FilterRegistrationBean jwtAuthenticationFilter() {
                  // ...
              }
      
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.apply(commonSecurityConfiguration())
                          .and().addFilter(jwtAuthenticationFilter().getFilter());
              }
          }
      
      
          /**
           * Common security configuration reused by all profiles.
           */
          public static class CommonSecurityConfiguration
                  extends AbstractHttpConfigurer<CommonSecurityConfiguration, HttpSecurity> {
      
              @Override
              public void init(HttpSecurity http) throws Exception {
                  // Your basic configuration here:
                  // http.cors().and()
                  // ...
              }
      
              public static CommonSecurityConfiguration commonSecurityConfiguration() {
                  return new CommonSecurityConfiguration();
              }
          }
      }
      

      另请参阅documentation 中的示例。

    2. 注入 Environment 对象并检查当前配置文件:

      @Configuration
      @EnableWebSecurity(debug = true)
      @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
      public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
      
          private final Environment environment;
      
          public SecurityConfiguration(Environment environment) {
              this.environment = environment;
          }
      
          @Profile("dev")
          @Bean
          public FilterRegistrationBean userIdAuthenticationFilter() {
              // ...
          }
      
          @Profile("!dev")
          @Bean
          public FilterRegistrationBean jwtAuthenticationFilter() {
              // ...
          }
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              // Your basic configuration here:
              // http.cors().and()
              // ...
      
              if (environment.acceptsProfiles("dev")) {
                  http.addFilter(userIdAuthenticationFilter().getFilter());
              } else {
                  http.addFilter(jwtAuthenticationFilter().getFilter());
              }
          }
      }
      

      或者,您可以为此使用应用程序属性而不是配置文件。

    【讨论】:

    • 谢谢,这很可能是我见过的最好的答案。你回答了问题(包括提问的和隐含的)并提供了很好的例子。我希望我能接受两次答案。干得好!
    【解决方案2】:

    我同样需要添加一个过滤器来测试我的应用程序,这会在请求中添加一些标头。

    @Autowired
    private HeaderAddingFilter headerAddingFilter;
    
     @Override
        protected void configure(HttpSecurity http) throws Exception {
         http.csrf().disable().
    
                        addFilterAt(new HeaderAuthenticationFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class).
                        addFilterBefore(headerAddingFilter,HeaderAuthenticationFilter.class)
        .....
    

    为了根据当前配置启用/禁用headerAddingFilter,我声明了一个接口HeaderAddingFilter,它有2个实现类:第一个有@Profile("local"),另一个有@Profile("!local"),它什么也不做,只是继续doFilter() 来电。

    附:故意注册以表明 Eien 的回答很棒且很有帮助。

    【讨论】:

      猜你喜欢
      • 2014-11-17
      • 1970-01-01
      • 2017-11-22
      • 2019-02-01
      • 1970-01-01
      • 2014-09-04
      • 2010-11-06
      • 2012-10-27
      • 1970-01-01
      相关资源
      最近更新 更多