我意识到 Spring 安全建立在过滤器链上,它将拦截请求、检测(不存在)身份验证、重定向到身份验证入口点或将请求传递给授权服务,并最终让请求命中 servlet 或抛出安全异常(未经身份验证或未经授权)。 DelegatingFitlerProxy 将这些过滤器粘合在一起。为了执行它们的任务,这些过滤器访问服务,例如 UserDetailsService 和 AuthenticationManager 。
链中的关键过滤器是(按顺序)
- SecurityContextPersistenceFilter(从 JSESSIONID 恢复身份验证)
- UsernamePasswordAuthenticationFilter(执行身份验证)
- ExceptionTranslationFilter(从 FilterSecurityInterceptor 捕获安全异常)
- FilterSecurityInterceptor(可能抛出认证授权异常)
我很困惑如何使用这些过滤器。是不是对于spring提供的form-login, UsernamePasswordAuthenticationFilter 只用于 /login ,后面的filter没有? 表单登录 命名空间元素是否自动配置这些过滤器?每个请求(是否经过身份验证)是否都到达非登录 url 的 FilterSecurityInterceptor ?
如果我想使用从登录中检索到的 JWT-token 来保护我的 REST API 怎么办? 我必须配置两个命名空间配置 http
标签,对吗?一个用于 /login with UsernamePasswordAuthenticationFilter
,另一个用于 REST url,自定义 JwtAuthenticationFilter
。
配置两个 http
元素会创建两个 springSecurityFitlerChains
吗? UsernamePasswordAuthenticationFilter
是否默认关闭,直到我声明 form-login
? How do I replace SecurityContextPersistenceFilter
with a filter which will obtain Authentication
from existing JWT-token
rather than JSESSIONID
?
原文由 Tuomas Toivonen 发布,翻译遵循 CC BY-SA 4.0 许可协议
Spring 安全过滤器链是一个非常复杂和灵活的引擎。
查看 当前稳定版本 4.2.1 文档,第 13.3 节过滤器排序,您可以看到整个过滤器链的过滤器组织:
现在,我将尝试逐一回答您的问题:
配置
<security-http>
部分后,您必须至少为每个部分提供一种身份验证机制。这必须是与我刚刚引用的 Spring Security 文档的 13.3 过滤器排序部分中的第 4 组匹配的过滤器之一。这是可以配置的最小有效 security:http 元素:
照做就行了,在过滤器链代理中配置了这些过滤器:
注意:我通过创建一个简单的 RestController 来获取它们,它 @Autowires FilterChainProxy 并返回它的内容:
在这里我们可以看到,仅通过使用一个最小配置声明
<security:http>
元素,所有默认过滤器都包含在内,但它们都不是身份验证类型(13.3 过滤器排序部分中的第 4 组)。所以这实际上意味着只需声明security:http
元素,SecurityContextPersistenceFilter、ExceptionTranslationFilter 和 FilterSecurityInterceptor 就会自动配置。事实上,应该配置一种身份验证处理机制,甚至安全命名空间 bean 处理声称,在启动期间抛出错误,但可以绕过它,在
<http:security>
中添加一个 entry-point-ref 属性如果我将基本的
<form-login>
添加到配置中,这样:现在,过滤器链将是这样的:
现在,这两个过滤器 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 和 org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 在 FilterChainProxy 中创建和配置。
所以,现在,问题是:
是的,它用于在请求与 UsernamePasswordAuthenticationFilter url 匹配的情况下尝试完成登录处理机制。这个 url 可以配置甚至改变它的行为来匹配每个请求。
您也可以在同一个 FilterchainProxy 中配置多个身份验证处理机制(例如 HttpBasic、CAS 等)。
不,表单登录元素配置 UsernamePasswordAUthenticationFilter,如果您不提供登录页面 url,它还会配置 org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,它以简单的自动生成登录结束页。
其他过滤器默认情况下是自动配置的,只需创建一个没有
security:"none"
属性的<security:http>
元素。每个请求都应该到达它,因为它是负责请求是否有权到达请求的 url 的元素。但是之前处理的一些过滤器可能会停止过滤器链处理而不是调用
FilterChain.doFilter(request, response);
。例如,如果请求没有 csrf 参数,CSRF 过滤器可能会停止过滤器链处理。不,你不是被迫这样做的。您可以在同一个 http 元素中声明
UsernamePasswordAuthenticationFilter
和JwtAuthenticationFilter
,但这取决于每个过滤器的具体行为。两种方式都可以,最后选择哪一种就看自己的喜好了。是的,这是真的
是的,您可以在我发布的每个配置中提出的过滤器中看到它
您可以避免使用 SecurityContextPersistenceFilter,只需在
<http:element>
中配置 会话策略。只需像这样配置:<security:http create-session="stateless" >
或者,在这种情况下,您可以用另一个过滤器覆盖它,在
<security:http>
元素中这样:编辑:
这最终取决于每个过滤器本身的实现,但事实是,后者的身份验证过滤器至少能够覆盖之前的过滤器最终进行的任何先前身份验证。
但这不一定会发生。我在安全 REST 服务中有一些生产案例,在这些案例中我使用了一种授权令牌,它既可以作为 Http 标头提供,也可以在请求正文中提供。因此,我配置了两个过滤器来恢复该令牌,一种是从 Http Header 中恢复,另一种是从自己的 rest 请求的请求体中恢复。事实上,如果一个 http 请求同时提供身份验证令牌作为 Http 标头和请求主体,两个过滤器都会尝试执行将其委托给管理器的身份验证机制,但可以很容易地避免简单地检查请求是否是已经在每个过滤器的
doFilter()
方法的开头进行了身份验证。拥有多个身份验证过滤器与拥有多个身份验证提供程序有关,但不要强求。在我之前公开的案例中,我有两个身份验证过滤器,但我只有一个身份验证提供程序,因为这两个过滤器创建相同类型的身份验证对象,因此在这两种情况下,身份验证管理器都将其委托给同一个提供程序。
与此相反,我也有一种情况,我只发布一个 UsernamePasswordAuthenticationFilter 但用户凭据都可以包含在 DB 或 LDAP 中,所以我有两个 UsernamePasswordAuthenticationToken 支持提供者,并且 AuthenticationManager 将任何身份验证尝试从过滤器委托给提供者seucentially 验证凭据。
所以,我认为很明显,无论是身份验证过滤器的数量决定身份验证提供者的数量,还是提供者的数量决定过滤器的数量。
我之前没有仔细研究过这个过滤器,但是在你上一个问题之后我一直在检查它的实现,并且像通常在 Spring 中一样,几乎所有的东西都可以配置、扩展或覆盖。
SecurityContextPersistenceFilter 在 SecurityContextRepository 实现中委托对 SecurityContext 的搜索。默认情况下,使用 HttpSessionSecurityContextRepository ,但这可以使用过滤器的构造函数之一进行更改。因此,最好编写一个适合您需要的 SecurityContextRepository 并在 SecurityContextPersistenceFilter 中配置它,相信它已经证明的行为而不是从头开始制作。