当前位置: 首页 > 知识库问答 >
问题:

Spring Security过滤链的工作原理

商飞翮
2023-03-14

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

链中的关键筛选器是(按顺序)

  • SecurityContextPersistenceFilter(从JSESSIONID还原身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全异常)
  • FilterSecurityInterceptor(可能引发身份验证和授权异常)

如果我想用JWT-token来保护我的REST API(它是从登录中检索的),该怎么办?我必须配置两个名称空间配置http标记,权限?一个用于/login,带有UsernamePasswordAuthenticationFilter,另一个用于REST URL,带有自定义的JWTauthenticationFilter

配置两个http元素是否会创建两个SpringSecurityFitlerChains?在声明form-login之前,UsernamePasswordAuthenticationFilter是否默认关闭?如何将SecurityContextPersistenceFilter替换为将从现有的jwt-token而不是JSessionID获得身份验证的筛选器?

共有1个答案

祁景山
2023-03-14

Spring安全过滤链是一个非常复杂和灵活的发动机。

链中的关键筛选器是(按顺序)

  • SecurityContextPersistenceFilter(从JSESSIONID还原身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全异常)
  • FilterSecurityInterceptor(可能引发身份验证和授权异常)

13.3滤波器排序

过滤器在链中定义的顺序非常重要。无论您实际使用的是哪种筛选器,顺序都应如下:

>

  • ChannelProcessingFilter,因为它可能需要重定向到其他协议

    ConcurrentSessionFilter,因为它使用SecurityContextHolder功能,并且需要更新SessionRegistry以反映来自主体的正在进行的请求

    身份验证处理机制-UsernamePasswordAuthenticationFilter、CasAuthenticationFilter、BasicAuthenticationFilter等-以便可以修改SecurityContextHolder以包含有效的身份验证请求令牌

    SecurityContextHolderAwareRequestFilter,如果您正在使用它将Spring Security感知HttpServletRequestWrapper安装到servlet容器中

    JaasApiIntegrationFilter,如果一个JaasAuthenticationToken在SecurityContextHolder中,这将处理FilterChain作为JaasAuthenticationToken中的主题

    RememmeAuthenticationFilter,这样,如果以前的身份验证处理机制没有更新SecurityContextHolder,并且请求呈现一个cookie,使Remement-Me服务能够发生,则会在那里放置一个合适的RemementAuthentication对象

    AnonymousAuthenticationFilter,这样,如果没有更早的身份验证处理机制更新SecurityContextHolder,则会将一个匿名身份验证对象放在那里

    ExceptionTranslationFilter,捕获任何Spring Security异常,以便返回HTTP错误响应或启动适当的AuthenticationEntryPoint

    FilterSecurityInterceptor,用于保护web URI并在拒绝访问时引发异常

    现在,我试着逐个回答你们的问题:

    这是可以配置的最低有效security: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 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"
        }
    

    现在,这两个过滤器org.springframework.security.web.authentication.usernamePasswordauthenticationfilter和org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingfilter在FilterChainProxy中创建和配置。

    那么,现在,问题是:

    是的,它用于在请求与UsernamePasswordAuthenticationFilter URL匹配的情况下尝试完成一个登录处理机制。这个url可以被配置,甚至可以改变它的行为,以匹配每一个请求。

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

    form-login命名空间元素是否自动配置这些筛选器?

    不,form-login元素配置UsernamePasswordAUthenticationFilter,如果您不提供登录页url,它还配置org.springframework.security.web.authentication.ui.defaultLoginPageGeneratingFilter,它以一个简单的自动生成的登录页结束。

    默认情况下,通过创建一个没有security:http>属性的 元素来自动配置其他筛选器。

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

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

    如果我想用JWT-token来保护我的REST API(它是从登录中检索的),该怎么办?我必须配置两个名称空间配置http标记,权限?另一个用于/login,使用UsernamePasswordAuthenticationFilter,另一个用于REST URL的,使用自定义的JWTauthenticationFilter

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

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

    是的,那是真的

    是否默认关闭UsernamePasswordAuthenticationFilter,直到我声明Form-Login?

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

    或者,在本例中,您可以用另一个筛选器覆盖它,在 元素中这样做:

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

    编辑:

    这最终取决于每个筛选器本身的实现,但事实是,后一个身份验证筛选器至少能够覆盖由前一个筛选器最终进行的任何前一个身份验证。

    但这不会发生的。我在安全的REST服务中有一些生产案例,其中我使用了一种授权令牌,它既可以作为Http头提供,也可以在请求体中提供。因此,我配置了两个过滤器来恢复令牌,一个是从Http头恢复,另一个是从自己的rest请求的请求体恢复。事实是,如果一个http请求将身份验证令牌作为http头并在请求主体中提供,那么两个筛选器都将尝试执行将其委托给管理器的身份验证机制,但是只要在每个筛选器的doFilter()方法开始时检查请求是否已经进行了身份验证,就可以很容易地避免这种情况。

    拥有多个身份验证筛选器与拥有多个身份验证提供程序有关,但不要强制。在前面公开的例子中,我有两个身份验证筛选器,但只有一个身份验证提供程序,因为这两个筛选器创建了相同类型的身份验证对象,所以在这两种情况下,身份验证管理器都将其委托给相同的提供程序。

    与此相反,我也有一个场景,其中我只发布一个UsernamePasswordAuthenticationFilter,但用户凭据都可以包含在DB或LDAP中,因此我有两个UsernamePasswordAuthenticationToken支持提供程序,并且AuthenticationManager将筛选器中的任何身份验证尝试安全地委托给提供程序以验证凭据。

    所以,我认为很清楚,身份验证过滤器的数量既不决定身份验证提供者的数量,也不决定提供者的数量决定过滤器的数量。

    此外,文档还指出,SecurityContextPersistenceFilter负责清理SecurityContext,这在线程池中很重要。如果我省略它或者提供自定义实现,我就不得不手动实现清理,对吗?定制外链的时候是不是有更多类似的Gotcha's?

    我以前没有仔细研究过这个过滤器,但在您的最后一个问题之后,我检查了它的实现,并且在Spring中,几乎所有的东西都可以被配置、扩展或覆盖。

    SecurityContextPersistenceFilter在SecurityContextRepository实现中委托对SecurityContext的搜索。默认情况下,使用HttpSessionSecurityContextRepository,但可以使用筛选器的构造函数之一对其进行更改。因此,编写一个适合您需要的SecurityContextRepersenceFilter并在SecurityContextPersistenceFilter中配置它可能会更好,相信它的已证明行为,而不是从头开始制作。

  •  类似资料:
    • Warning This is a cut 'n paste job from an email (<022501c1c529$f63a9550$7f00000a@KOJ>) and only reformatted for better readability. It's not up to date but may be a good start for further research. F

    • 我试图理解过滤器链接。正如这个问题所定义的 所有过滤器都被链接(按照它们在web.xml中定义的顺序)。chain.doFilter()正在进行到链中的下一个元素。链的最后一个元素是目标资源/servlet。 我很想知道容器中的场景背后,容器是如何处理过滤器链接的。有人能解释一下过滤器链接是如何在容器内处理的吗?

    • 主要内容:FilterChain 接口,Filter 链的拦截过程,Filter 链中 Filter 的执行顺序,示例在 Web 应用中,可以部署多个 Filter,若这些 Filter 都拦截同一目标资源,则它们就组成了一个 Filter 链(也称过滤器链)。过滤器链中的每个过滤器负责特定的操作和任务,客户端的请求在这些过滤器之间传递,直到传递给目标资源。 FilterChain 接口 javax.servlet 包中提供了一个 FilterChain 接口,该接口由容器实现。容器将其实例对象

    • 当我尝试使用时,我得到一个异常。下面是堆栈跟踪: 编辑二:

    • 在过去的三天里,我一直在与错误“跨源请求被阻止:相同的源策略不允许读取http://localhost:8080/demomongo/templateapp/login上的远程资源。”(原因:缺少CORS头'Access-Control-Allow-Origin')。