首页 文章

Spring Security Filter Chain的工作原理

提问于
浏览
64

我意识到Spring安全性构建在过滤器链上,它将拦截请求,检测(缺少)身份验证,重定向到身份验证入口点或将请求传递给授权服务,并最终让请求命中servlet或抛出安全性异常(未经认证或未经授权) . DelegatingFitlerProxy将这些过滤器粘合在一起 . 为了执行他们的任务,这些过滤器访问服务,例如UserDetailsService和AuthenticationManager .

链中的关键过滤器(按顺序)

  • SecurityContextPersistenceFilter(从JSESSIONID恢复身份验证)

  • UsernamePasswordAuthenticationFilter(执行身份验证)

  • ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全性异常)

  • FilterSecurityInterceptor(可能会抛出身份验证和授权异常)

我很困惑如何使用这些过滤器 . 是否为spring提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,后面的过滤器不是? form-login名称空间元素是否自动配置这些过滤器?是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

What if I want to secure my REST API with JWT-token, which is retrieved from login? 我必须配置两个命名空间配置 http 标签,权限?其他一个用于/ login使用 UsernamePasswordAuthenticationFilter ,另一个用于REST URL,使用自定义 JwtAuthenticationFilter .

配置两个 http 元素是否会创建两个 springSecurityFitlerChainsUsernamePasswordAuthenticationFilter 默认关闭,直到我声明 form-login ?如何将 SecurityContextPersistenceFilter 替换为一个,它将从现有的 JWT-token 而不是 JSESSIONID 获得 Authentication

2 回答

  • 4

    Spring安全过滤器链是一个非常复杂和灵活的引擎 .

    链中的关键过滤器(按顺序)SecurityContextPersistenceFilter(从JSESSIONID恢复身份验证)UsernamePasswordAuthenticationFilter(执行身份验证)ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全性异常)FilterSecurityInterceptor(可能抛出身份验证和授权异常)

    查看current stable release 4.2.1 documentation13.3 Filter Ordering部分,您可以看到整个过滤器链的过滤器组织:

    13.3过滤器排序过滤器在链中定义的顺序非常重要 . 无论您实际使用哪个过滤器,顺序应如下所示:ChannelProcessingFilter,因为它可能需要重定向到不同的协议SecurityContextPersistenceFilter,因此可以在Web请求开始时在SecurityContextHolder中设置SecurityContext,以及任何当Web请求结束(准备好与下一个Web请求一起使用)ConcurrentSessionFilter时,可以将对SecurityContext的更改复制到HttpSession,因为它使用SecurityContextHolder功能并需要更新SessionRegistry以反映来自主体身份验证处理机制的持续请求 - UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等 - 以便可以修改SecurityContextHolder以包含有效的身份验证请求令牌SecurityContextHolderAwareRequestFilter,如果您使用它将Spring安全感知的HttpServletRequestWrapper安装到您的servlet容器中JaasApiIntegrationFilter,如果JaasAuthenticationToken在SecurityContextHolder中,这将在JaasAuthenticationToken RememberMeAuthenticationFilter中处理FilterChain作为Subject,这样如果没有更早的身份验证处理机制更新SecurityContextHolder,并且请求提供了一个启用remember-me服务的cookie,一个合适的记忆身份验证对象将放在那里AnonymousAuthenticationFilter,这样如果没有更早的身份验证处理机制更新SecurityContextHolder,匿名身份验证对象将被放在那里ExceptionTranslationFilter,以捕获任何Spring Security异常,以便可以返回HTTP错误响应或可以启动适当的AuthenticationEntryPoint FilterSecurityInterceptor,以保护Web URI并在访问被拒绝时引发异常

    现在,我将逐一尝试你的问题:

    我很困惑如何使用这些过滤器 . 是否为spring提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,后面的过滤器不是? form-login名称空间元素是否自动配置这些过滤器?是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

    配置 <security-http> 部分后,对于每个部分,您必须至少提供一种身份验证机制 . 这必须是我刚刚引用的Spring Security文档的13.3 Filter Ordering部分中与第4组匹配的过滤器之一 .

    这是最低有效安全性:http元素可以配置:

    <security:http authentication-manager-ref="mainAuthenticationManager" 
                   entry-point-ref="serviceAccessDeniedHandler">
        <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    </security:http>
    

    只是这样做,这些过滤器配置在过滤链代理:

    {
            "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
            "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
            "3": "org.springframework.security.web.header.HeaderWriterFilter",
            "4": "org.springframework.security.web.csrf.CsrfFilter",
            "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
            "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
            "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
            "8": "org.springframework.security.web.session.SessionManagementFilter",
            "9": "org.springframework.security.web.access.ExceptionTranslationFilter",
            "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
        }
    

    注意:我通过创建一个简单的RestController来获取它们,它们@Autowires FilterChainProxy并返回它的内容:

    @Autowired
        private FilterChainProxy filterChainProxy;
    
        @Override
        @RequestMapping("/filterChain")
        public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
            return this.getSecurityFilterChainProxy();
        }
    
        public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
            Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
            int i = 1;
            for(SecurityFilterChain secfc :  this.filterChainProxy.getFilterChains()){
                //filters.put(i++, secfc.getClass().getName());
                Map<Integer, String> filters = new HashMap<Integer, String>();
                int j = 1;
                for(Filter filter : secfc.getFilters()){
                    filters.put(j++, filter.getClass().getName());
                }
                filterChains.put(i++, filters);
            }
            return filterChains;
        }
    

    在这里我们可以看到,仅通过使用一个最小配置声明 <security:http> 元素,包含所有默认过滤器,但它们都不是认证类型(13.3过滤器排序部分中的第4组) . 所以它实际上意味着只需声明 security:http 元素,SecurityContextPersistenceFilter,ExceptionTranslationFilter和FilterSecurityInterceptor就会自动配置 .

    实际上,应该配置一个身份验证处理机制,甚至安全命名空间bean处理它的声明,在启动期间抛出错误,但可以绕过在 <http:security> 中添加entry-point-ref属性

    如果我在配置中添加一个基本 <form-login> ,这样:

    <security:http authentication-manager-ref="mainAuthenticationManager">
        <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
        <security:form-login />
    </security:http>
    

    现在,filterChain将是这样的:

    {
            "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
            "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
            "3": "org.springframework.security.web.header.HeaderWriterFilter",
            "4": "org.springframework.security.web.csrf.CsrfFilter",
            "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
            "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
            "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
            "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
            "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
            "10": "org.springframework.security.web.session.SessionManagementFilter",
            "11": "org.springframework.security.web.access.ExceptionTranslationFilter",
            "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
        }
    

    现在,在FilterChainProxy中创建并配置了这两个过滤器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter和org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter .

    所以,现在,问题:

    对于 spring 提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,而后者的过滤器不是?

    是的,它用于尝试完成登录处理机制,以防请求与UsernamePasswordAuthenticationFilter url匹配 . 可以配置此URL或甚至更改其行为以匹配每个请求 .

    您也可以在同一FilterchainProxy中配置多个身份验证处理机制(例如HttpBasic,CAS等) .

    form-login名称空间元素是否自动配置这些过滤器?

    不,form-login元素配置UsernamePasswordAUthenticationFilter,如果你没有提供登录页面网址,它还会配置org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,它以简单的自动生成登录结束页 .

    默认情况下,只需创建没有 security:"none" 属性的 <security:http> 元素,即可自动配置其他过滤器 .

    是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

    每个请求都应该到达它,因为它是负责请求是否有权访问所请求的URL的元素 . 但之前处理的一些过滤器可能会停止过滤器链处理而不会调用 FilterChain.doFilter(request, response); . 例如,如果请求没有csrf参数,CSRF过滤器可能会停止过滤器链处理 .

    如果我想使用从登录检索的JWT令牌来保护我的REST API,该怎么办?我必须配置两个命名空间配置http标签,权限?另一个用于/ login用UsernamePasswordAuthenticationFilter,另一个用于REST URL,带有自定义JwtAuthenticationFilter .

    不,你不是被迫这样做的 . 您可以在同一个http元素中声明 UsernamePasswordAuthenticationFilterJwtAuthenticationFilter ,但这取决于每个过滤器的具体行为 . 这两种方法都是可能的,哪种方法最终取决于自己的喜好 .

    配置两个http元素是否会创建两个springSecurityFitlerChains?

    是的,这是真的

    默认情况下,UsernamePasswordAuthenticationFilter是否已关闭,直到我声明表单登录?

    是的,你可以在我发布的每个配置中提出的过滤器中看到它

    如何将SecurityContextPersistenceFilter替换为一个,它将从现有的JWT-token而不是JSESSIONID获取身份验证?

    您可以避免使用SecurityContextPersistenceFilter,只需在 <http:element> 中配置session strategy即可 . 只需像这样配置:

    <security:http create-session="stateless" >

    或者,在这种情况下,您可以使用另一个过滤器覆盖它,这样在 <security:http> 元素内:

    <security:http ...>  
       <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>    
    </security:http>
    <beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
    

    编辑:

    关于“您可以在同一FilterchainProxy中配置多个身份验证处理机制”的一个问题 . 如果声明多个(Spring实现)身份验证过滤器,后者是否会覆盖第一个执行的身份验证?这与多个身份验证提供程序有何关系?

    这最终取决于每个过滤器本身的实现,但事实上后者的认证过滤器至少能够覆盖最终由前面的过滤器进行的任何先前认证 .

    但是,如果一个http请求将Http标头作为Http标头并在请求体内部提供,那么两个过滤器将尝试执行将其委托给管理器的身份验证机制,但这可以很容易地避免简单地检查如果请求已经在每个过滤器的 doFilter() 方法开始时已经过身份验证 .

    拥有多个身份验证筛选器与拥有多个身份验证提供程序有关,但不要强制它 . 在我之前曝光的情况下,我有两个身份验证过滤器,但我只有一个身份验证提供程序,作为两个过滤器创建相同类型的Authentication对象,因此在这两种情况下,身份验证管理器都会将其委派给同一个提供程序 .

    与此相反,我也有一个场景,我只发布一个UsernamePasswordAuthenticationFilter,但用户凭据都可以包含在DB或LDAP中,所以我有两个UsernamePasswordAuthenticationToken支持提供程序,AuthenticationManager委托从过滤器到提供程序的任何身份验证尝试有意识地验证凭证 .

    因此,我认为很明显,身份验证过滤器的数量既不会决定身份验证提供程序的数量,也不会决定提供程序的数量来确定过滤器的数量 .

    另外,文档说明SecurityContextPersistenceFilter负责清理SecurityContext,这对于线程池很重要 . 如果我省略它或提供自定义实现,我必须手动实现清理,对吧?在定制链条时是否有更多类似的问题?

    我之前没有仔细研究过这个过滤器,但是在你上一个问题之后我一直在检查它的实现,并且通常在Spring中,几乎所有东西都可以配置,扩展或覆盖 .

    SecurityContextPersistenceFilter委托在SecurityContextRepository实现中搜索SecurityContext . 默认情况下,使用HttpSessionSecurityContextRepository,但可以使用过滤器的一个构造函数更改此值 . 因此,最好编写一个符合您需求的SecurityContextRepository,并在SecurityContextPersistenceFilter中对其进行配置,相信它已被证实的行为,而不是从头开始全部制作 .

  • 119

    UsernamePasswordAuthenticationFilter仅用于/ login,而后者的过滤器不是?

    不, UsernamePasswordAuthenticationFilter 扩展 AbstractAuthenticationProcessingFilter ,这包含 RequestMatcher ,这意味着您可以定义自己的处理URL,此过滤器只处理 RequestMatcher 匹配请求URL,默认处理URL为 /login .

    如果 UsernamePasswordAuthenticationFilter 执行 chain.doFilter(request, response); ,以后的过滤器仍然可以处理请求 .

    关于core fitlers的更多细节

    form-login名称空间元素是否自动配置这些过滤器?

    UsernamePasswordAuthenticationFilter<form-login> 创建,这些是Standard Filter Aliases and Ordering

    是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

    这取决于之前的装配工是否成功,但 FilterSecurityInterceptor 通常是最后的装配工 .

    配置两个http元素是否会创建两个springSecurityFitlerChains?

    是的,每个fitlerChain都有 RequestMatcher ,如果 RequestMatcher 与请求匹配,请求将由fitler链中的fitlers处理 .

    如果您不配置模式,则默认 RequestMatcher 匹配所有请求,或者您可以配置特定URL( <http pattern="/rest/**" ) .

    如果你想了解更多关于fitlers的信息,我想你可以在spring security中查看源代码 . doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

相关问题