简体   繁体   English

如何基于@Profile为spring-security添加过滤器?

[英]How do I add a filter to spring-security based on a @Profile?

I am attempting to define two different beans (both extending AbstractPreAuthenticatedProcessingFilter): one for grabbing a header off of the request (such as USER_ID) while a "development" profile is active, and a second to grab a JWT off of the request header while the "development" profile is not active. 我试图定义两个不同的bean(都扩展AbstractPreAuthenticatedProcessingFilter):一个用于在“开发”配置文件处于活动状态时从请求中获取头(例如USER_ID),另一个用于从请求头中获取JWT “开发”配置文件激活。 (Conceptually though, I'm really just trying to programmatically register filters based on the existence of the bean itself) At the moment, I'm not even trying to use profiles, since I'm having an issue with getting the headers to automatically be registered in the appropriate filter chain. (从概念上讲,我真的只是尝试以编程方式根据bean本身的存在来注册过滤器)目前,我甚至都没有尝试使用配置文件,因为我遇到了自动获取标头的问题在适当的过滤链中注册。

The app uses Spring-Boot 2.0.0.RELEASE, configured to use an embedded Tomcat, and the service is annotated as a @RestController. 该应用程序使用Spring-Boot 2.0.0.RELEASE,配置为使用嵌入式Tomcat,该服务注释为@RestController。 Below is a simplified version of my SecurityConfig class: 下面是我的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()
    ;


 }

}

As you can see, I've defined my filter beans, and they work when appliced to the correct chain... The problem I'm seeing is that spring appears to register to the filters somewhere , but when I make a call to service endpoint, it never calls the filter code I added. 正如你所看到的,我已经定义了我的过滤器bean,它们在应用于正确的链时起作用......我看到的问题是spring似乎在某处注册到过滤器,但是当我打电话给服务时端点,它从不调用我添加的过滤器代码。

Log output from running the app that seemingly indicates that the filters are being located... 运行应用程序的日志输出似乎表明过滤器正在定位...

 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

Now that the app is running, when I try to access the service I see spring dump the following output (with debug on): 现在应用程序正在运行,当我尝试访问该服务时,我看到spring转储以下输出(启用调试):

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

Given that output, it indicates to me that the security filter chain does not have my filters applied. 鉴于输出,它表明,我认为安全过滤器链没有应用我的过滤器。

Perhaps more telling is that if you'll notice in my configuration code snippet there are two lines commented out (this is where I was manually adding the filter(s) before I tried to get fancy with the profile based detection). 也许更有说服力的是,如果您在我的配置代码片段中注意到有两行被注释掉(这是我在尝试使用基于配置文件的检测之前手动添加过滤器的地方)。 If I uncomment the line that adds the JWT filter (aka. manually registering the filter, rather than rely on detection), everything seems to work as intended. 如果我取消注释添加JWT过滤器的行(也就是手动注册过滤器,而不是依赖于检测),一切似乎都按预期工作。 Looking at the debug output, I now see the following when calling the endpoint after manually adding the filter (notice that the JwtAuthenticationFilter is now present in the security filter chain): 查看调试输出,我现在在手动添加过滤器后调用端点时看到以下内容(注意JwtAuthenticationFilter现在出现在安全过滤器链中):

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

There are really two questions I'm asking... I'm obviously not understanding the various filter chains that spring-boot/spring-security sets up, so what is the difference between the beans being registered during the application start up in the normal filter chain, vs the beans being registered with the spring-security filter chain? 我问的确有两个问题......我显然不理解spring-boot / spring-security设置的各种过滤器链,所以在应用程序启动期间注册的bean之间的区别是什么?正常的过滤链,与使用spring-security过滤链注册的bean相比? Can someone point out what I'm doing wrong and why? 有人可以指出我做错了什么,为什么?

What is the correct way to register these "optional" beans? 注册这些“可选”bean的正确方法是什么? (Optional by virtue of possibly not being there dependent on the active profile.) (可选,因为可能不依赖于活动配置文件。)

Thanks! 谢谢!

From the article about Spring Security architecture: 从关于Spring Security架构的文章

Spring Security is installed as a single Filter in the chain, and its concerete type is FilterChainProxy, for reasons that will become apparent soon. Spring Security作为链中的单个Filter安装,其concerete类型是FilterChainProxy,原因很快就会显现出来。 In a Spring Boot app the security filter is a @Bean in the ApplicationContext, and it is installed by default so that it is applied to every request. 在Spring Boot应用程序中,安全筛选器是ApplicationContext中的@Bean,默认情况下会安装它,以便将其应用于每个请求。

There can be multiple filter chains all managed by Spring Security in the same top level FilterChainProxy and all unknown to the container. Spring Security可以在同一顶级FilterChainProxy中管理多个过滤器链,并且容器都是未知的。 The Spring Security filter contains a list of filter chains, and dispatches a request to the first chain that matches it. Spring Security过滤器包含过滤器链列表,并将请求分派给与其匹配的第一个链。

Note also that: 还要注意:

The fact that all filters internal to Spring Security are unknown to the container is important, especially in a Spring Boot application, where all @Beans of type Filter are registered automatically with the container by default. Spring安全内部的所有过滤器都不为容器所知,这一点很重要,尤其是在Spring Boot应用程序中,默认情况下,所有类型为Filter的@Beans都会自动注册到容器中。 So if you want to add a custom filter to the security chain, you need to either not make it a @Bean or wrap it in a FilterRegistrationBean that explicitly disables the container registration. 因此,如果要向安全链添加自定义筛选器,则需要将其设置为@Bean,或者将其包装在显式禁用容器注册的FilterRegistrationBean中。

So, when you define a filter as a Spring bean, it is registered with the servlet container automatically, but not with the Spring Security filter chain. 因此,当您将过滤器定义为Spring bean时,它会自动注册到servlet容器,但不会注册Spring Security过滤器链。 That is why you need to add it to the Spring Security chain explicitly using addFilter method. 这就是为什么你需要使用addFilter方法显式地将它添加到Spring Security链。 You also need to disable auto-registration in the servlet container or the filter will be called twice. 您还需要在servlet容器中禁用自动注册,否则将调用过滤器两次。

See also: 也可以看看:

As for profiles there are at least two ways to do what you need: 至于配置文件,至少有两种方法可以满足您的需求:

  1. Extend AbstractHttpConfigurer and move common security configuration there. 扩展AbstractHttpConfigurer并在那里移动常见的安全配置。 After that create a separate security configuration for each profile: 之后,为每个配置文件创建单独的安全配置:

     @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(); } } } 

    See also the example in the documentation . 另请参阅文档中的示例。

  2. Inject the Environment object and check the current profile: 注入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()); } } } 

    Alternatively you can use application property instead of profile for this. 或者,您可以使用应用程序属性而不是配置文件。

I have the same need to add a filter for testing my app, which adds some header in request. 我有同样的需要添加一个过滤器来测试我的应用程序,它在请求中添加了一些标题。

@Autowired
private HeaderAddingFilter sudirHeaderAddingFilter;

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

                    addFilterAt(new HeaderAuthenticationFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class).
                    addFilterBefore(headerAddingFilter,HeaderAuthenticationFilter.class)
    .....

In order to enable/disable headerAddingFilter according to current profile, I declared an interface HeaderAddingFilter with 2 implementation classes: the first has @Profile("local") , another has @Profile("!local") , and it does nothing, just continues with doFilter() call. 为了根据当前配置文件启用/禁用headerAddingFilter ,我声明了一个带有2个实现类的接口HeaderAddingFilter :第一个有@Profile("local") ,另一个有@Profile("!local") ,它什么也没做,只是继续doFilter()调用。

PS Intentionally registered to state that Eien's answer is great and helpful. PS有意注册表示Eien的答案很棒且乐于助人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM