小编典典

Spring Security 过滤器链是如何工作的

all

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

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

  • SecurityContextPersistenceFilter(从 JSESSIONID 恢复身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从 FilterSecurityInterceptor 捕获安全异常)
  • FilterSecurityInterceptor(可能抛出认证和授权异常)

我很困惑如何使用这些过滤器。是不是对于spring提供的form-login, UsernamePasswordAuthenticationFilter
只用于 /login ,而后面的过滤器不是? form-login 命名空间元素是否自动配置这些过滤器?是否每个请求(是否经过身份验证)都到达非登录
URL 的 FilterSecurityInterceptor ?

如果我想使用 从登录中检索到的 JWT-token来保护我的 REST API 怎么办?
我必须配置两个命名空间配置http标签,权利?一个用于 /login
UsernamePasswordAuthenticationFilter另一个用于 REST url,带有 custom
JwtAuthenticationFilter

配置两个http元素会创建两个springSecurityFitlerChains吗?默认情况下是UsernamePasswordAuthenticationFilter关闭的,直到我声明form- login?如何用将从现有而不是从现有SecurityContextPersistenceFilter的过滤器中获得的过滤器替换?Authentication``JWT- token``JSESSIONID


阅读 75

收藏
2022-07-28

共1个答案

小编典典

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

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

  • SecurityContextPersistenceFilter(从 JSESSIONID 恢复身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从 FilterSecurityInterceptor 捕获安全异常)
  • FilterSecurityInterceptor(可能抛出认证和授权异常)

查看当前稳定版本 4.2.1 文档,第13.3
节过滤器排序
,您可以看到整个过滤器链的过滤器组织:

13.3 过滤器排序

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

  1. ChannelProcessingFilter ,因为它可能需要重定向到不同的协议

  2. SecurityContextPersistenceFilter ,因此可以在 Web 请求开始时在
    SecurityContextHolder 中设置 SecurityContext ,并且可以在 Web 请求结束时将 SecurityContext
    的任何更改复制到 HttpSession (准备用于下一个 Web 请求)

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

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

  5. SecurityContextHolderAwareRequestFilter ,如果您使用它来将 Spring Security
    感知 HttpServletRequestWrapper 安装到您的 servlet 容器中

  6. JaasApiIntegrationFilter ,如果 JaasAuthenticationToken
    SecurityContextHolder 中,这会将 FilterChain 作为 JaasAuthenticationToken 中的主题
    处理
    __

  7. RememberMeAuthenticationFilter ,因此如果没有更早的身份验证处理机制更新
    SecurityContextHolder,并且请求提供了一个启用 remember-me 服务的
    cookie,则将在那里放置一个合适的记忆身份验证对象

  8. AnonymousAuthenticationFilter
    ,这样如果之前的认证处理机制没有更新SecurityContextHolder,就会放一个匿名的Authentication对象

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

  10. FilterSecurityInterceptor ,用于保护 Web URI 并在访问被拒绝时引发异常

现在,我将尝试一一回答您的问题:

我很困惑如何使用这些过滤器。是不是spring提供的form-
login,UsernamePasswordAuthenticationFilter只用于/login,后面的过滤器不是?form-login
命名空间元素是否自动配置这些过滤器?是否每个请求(是否经过身份验证)都到达非登录 URL 的 FilterSecurityInterceptor?

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

这是可以配置的最低有效 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>使用一个最低配置声明元素,所有默认过滤器都包括在内,但它们都不是身份验证类型(13.3
过滤器排序部分中的第 4
组)。所以它实际上意味着仅仅通过声明security:http元素,SecurityContextPersistenceFilter、ExceptionTranslationFilter
和 FilterSecurityInterceptor 都是自动配置的。

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

如果我<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"
    }

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

所以,现在,问题:

是不是spring提供的form-
login,UsernamePasswordAuthenticationFilter只用于/login,后面的过滤器不是?

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

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

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

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

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

是否每个请求(是否经过身份验证)都到达非登录 URL 的 FilterSecurityInterceptor?

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

如果我想使用从登录中检索到的 JWT-token 来保护我的 REST API,该怎么办?我必须配置两个命名空间配置http标签,权限?另一个用于
/login UsernamePasswordAuthenticationFilter,另一个用于 REST url,带有 custom
JwtAuthenticationFilter

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

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

是的,这是真的

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

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

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

您可以避免 SecurityContextPersistenceFilter,只需<http:element>. 只需像这样配置:

<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
实现)身份验证过滤器,后者会覆盖第一个执行的身份验证吗?这与拥有多个身份验证提供程序有何关系?

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

但这不一定会发生。我在安全 REST 服务中有一些生产案例,我使用一种授权令牌,可以作为 Http
标头或在请求正文中提供。因此,我配置了两个过滤器来恢复该令牌,一种情况下来自 Http Header,另一种来自自己的休息请求的请求正文。确实,如果一个
http 请求在 Http
标头和请求正文中都提供了该身份验证令牌,则两个过滤器都将尝试执行将其委托给管理器的身份验证机制,但可以很容易地避免简单地检查请求是否是已经在doFilter()每个过滤器的方法开始时进行了身份验证。

拥有多个身份验证过滤器与拥有多个身份验证提供者有关,但不要强求。在我之前公开的情况下,我有两个身份验证过滤器,但我只有一个身份验证提供者,因为两个过滤器都创建相同类型的身份验证对象,因此在这两种情况下,身份验证管理器都将其委托给同一个提供者。

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

因此,我认为很明显,身份验证过滤器的数量都不能决定身份验证提供者的数量,提供者的数量也不能决定过滤器的数量。

此外,文档指出 SecurityContextPersistenceFilter 负责清理
SecurityContext,这对线程池很重要。如果我省略它或提供自定义实现,我必须手动实现清理,对吗?自定义链条时是否有更多类似的陷阱?

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

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

2022-07-28