我意识到 Spring 安全性建立在过滤器链上,它将拦截请求,检测(不存在)身份验证,重定向到身份验证入口点或将请求传递给授权服务,最终让请求命中 servlet 或抛出安全异常(未经身份验证或未经授权)。 DelegatingFitlerProxy 将这些过滤器粘合在一起。为了执行它们的任务,这些过滤器访问诸如 UserDetailsService 和 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,带有自定义 JwtAuthenticationFilter
。
配置两个 http
元素会创建两个 springSecurityFitlerChains
吗?在我声明 form-login
之前是否默认关闭 UsernamePasswordAuthenticationFilter
?如何将 SecurityContextPersistenceFilter
替换为将从现有 JWT-token
而不是 JSESSIONID
获得 Authentication
的过滤器?
Spring 安全过滤器链是一个非常复杂和灵活的引擎。
链中的关键过滤器(按顺序) SecurityContextPersistenceFilter(从 JSESSIONID 恢复 Authentication) UsernamePasswordAuthenticationFilter(执行身份验证) ExceptionTranslationFilter(从 FilterSecurityInterceptor 捕获安全异常) FilterSecurityInterceptor(可能抛出身份验证和授权异常)
查看 current stable release 4.2.1 documentation 的 13.3 Filter Ordering 部分,您可以看到整个过滤器链的过滤器组织:
13.3 过滤器排序 过滤器在链中定义的顺序非常重要。不管你实际使用的是哪个过滤器,顺序应该如下:ChannelProcessingFilter,因为它可能需要重定向到不同的协议SecurityContextPersistenceFilter,所以可以在web请求开始的时候在SecurityContextHolder中设置一个SecurityContext,任何一个当 Web 请求结束时(准备好与下一个 Web 请求一起使用)ConcurrentSessionFilter,对 SecurityContext 的更改可以复制到 HttpSession,因为它使用 SecurityContextHolder 功能并且需要更新 SessionRegistry 以反映来自主体身份验证处理机制的正在进行的请求 - UsernamePasswordAuthenticationFilter、CasAuthenticationFilter、BasicAuthenticationFilter 等 - 以便可以修改 SecurityContextHolder 以包含有效的身份验证请求令牌 SecurityContextHolderAwareRequestFilter,如果您使用它将 Spring Security 感知 HttpServletRequestWrapper 安装到您的 servlet 容器中JaasApiIntegrationFilter,如果 JaasAuthenticationToken 在 SecurityContextHolder 中,这将把 FilterChain 作为 JaasAuthenticationToken RememberMeAuthenticationFilter 中的 Subject 处理,因此如果没有更早的身份验证处理机制更新 SecurityContextHolder,并且请求提供了一个启用 remember-me 服务的 cookie,一个合适的被记住的 Authentication 对象将被放在那里 AnonymousAuthenticationFilter,这样如果没有更早的身份验证处理机制更新 SecurityContextHolder,一个匿名 Authentication 对象将放在那里 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 组匹配的过滤器之一。
这是可以配置的最低有效 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 都会为此处理声明,在启动期间抛出错误,但可以绕过它在 <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"
}
现在,这两个过滤器 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:"none"
属性的 <security:http>
元素即可自动配置其他过滤器。
是否每个请求(是否经过身份验证)都到达非登录 URL 的 FilterSecurityInterceptor?
每个请求都应该到达它,因为它是负责处理请求是否有权到达请求的 url 的元素。但是之前处理的某些过滤器可能会停止过滤器链处理,只是没有调用 FilterChain.doFilter(request, response);
。例如,如果请求没有 csrf 参数,则 CSRF 过滤器可能会停止过滤器链处理。
如果我想使用从登录中检索到的 JWT-token 来保护我的 REST API,该怎么办?我必须配置两个命名空间配置http标签,权限?另一个用于 /login,带有 UsernamePasswordAuthenticationFilter,另一个用于 REST url,带有自定义 JwtAuthenticationFilter。
不,你不是被迫这样做的。您可以在同一个 http 元素中同时声明 UsernamePasswordAuthenticationFilter
和 JwtAuthenticationFilter
,但这取决于每个过滤器的具体行为。这两种方法都是可能的,最终选择哪一种取决于自己的喜好。
配置两个http元素会创建两个springSecurityFitlerChains吗?
是的,这是真的
UsernamePasswordAuthenticationFilter 是否默认关闭,直到我声明表单登录?
是的,您可以在我发布的每个配置中提出的过滤器中看到它
如何将 SecurityContextPersistenceFilter 替换为一个,它将从现有的 JWT 令牌而不是 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 实现)身份验证过滤器,后者会覆盖第一个执行的身份验证吗?这与拥有多个身份验证提供程序有何关系?
这最终取决于每个过滤器本身的实现,但事实是,后一个身份验证过滤器至少能够覆盖任何先前由前面的过滤器最终进行的身份验证。
但这不一定会发生。我在安全 REST 服务中有一些生产案例,我使用一种授权令牌,可以作为 Http 标头或在请求正文中提供。因此,我配置了两个过滤器来恢复该令牌,一种情况下来自 Http Header,另一种情况下来自自己的休息请求的请求正文。确实,如果一个 http 请求在 Http 标头和请求正文中都提供了该身份验证令牌,则两个过滤器都会尝试执行将其委托给管理器的身份验证机制,但可以很容易地避免简单地检查请求是否是已经在每个过滤器的 doFilter()
方法开始时进行了身份验证。
拥有多个身份验证过滤器与拥有多个身份验证提供者有关,但不要强求。在我之前公开的情况下,我有两个身份验证过滤器,但我只有一个身份验证提供者,因为两个过滤器都创建相同类型的身份验证对象,因此在这两种情况下,身份验证管理器都将其委托给同一个提供者。
与此相反,我也有一个场景,我只发布一个 UsernamePasswordAuthenticationFilter 但用户凭据都可以包含在 DB 或 LDAP 中,所以我有两个 UsernamePasswordAuthenticationToken 支持提供程序,并且 AuthenticationManager 将来自过滤器的任何身份验证尝试委托给提供程序安全地验证凭据。
因此,我认为很明显,身份验证过滤器的数量既不能决定身份验证提供者的数量,也不能由提供者的数量决定过滤器的数量。
此外,文档指出 SecurityContextPersistenceFilter 负责清理 SecurityContext,这对线程池很重要。如果我省略它或提供自定义实现,我必须手动实现清理,对吗?自定义链条时是否有更多类似的陷阱?
我之前没有仔细研究过这个过滤器,但是在你的最后一个问题之后,我一直在检查它的实现,并且通常在 Spring 中,几乎所有东西都可以配置、扩展或覆盖。
SecurityContextPersistenceFilter 在 SecurityContextRepository 实现中委托对 SecurityContext 的搜索。默认情况下,使用 HttpSessionSecurityContextRepository,但这可以使用过滤器的构造函数之一进行更改。因此,最好编写一个符合您需求的 SecurityContextRepository 并在 SecurityContextPersistenceFilter 中对其进行配置,相信它已证明的行为,而不是从头开始制作所有内容。
Spring Security 是一个基于过滤器的框架,它在您的应用程序之前根据代理过滤器或 Spring 托管 bean 植入一个 WALL(HttpFireWall)。您的请求必须通过多个过滤器才能到达您的 API。
Spring Security 中的执行顺序
WebAsyncManagerIntegrationFilter 提供 SecurityContext 和 Spring Web 的 WebAsyncManager 之间的集成。 SecurityContextPersistenceFilter 此过滤器将仅对每个请求执行一次,使用在请求之前从配置的 SecurityContextRepository 获得的信息填充 SecurityContextHolder,并在请求完成并清除上下文持有者后将其存储回存储库中。检查现有会话的请求。如果是新请求,则将创建 SecurityContext,否则如果请求具有会话,则将从存储库中获取现有的安全上下文。 HeaderWriterFilter 过滤器实现将标题添加到当前响应。 LogoutFilter 如果请求 url 是 /logout(对于默认配置)或者如果请求 url 与 LogoutConfigurer 中配置的 RequestMatcher 匹配,则
清除安全上下文。
使会话无效
删除所有在 LogoutConfigurer 中配置 cookie 名称的 cookie
重定向到默认注销成功 url / 或配置的注销成功 url 或调用 logoutSuccessHandler 配置。
用户名密码身份验证过滤器
对于除 loginProcessingUrl 以外的任何请求 url,此过滤器不会进一步处理,但过滤器链会继续。
如果请求的 URL 匹配(必须是 HTTP POST)默认 /login 或匹配 FormLoginConfigurer 中配置的 .loginProcessingUrl(),则 UsernamePasswordAuthenticationFilter 尝试进行身份验证。
默认登录表单参数是用户名和密码,可以被 usernameParameter(String)、passwordParameter(String) 覆盖。
设置 .loginPage() 覆盖默认值
在尝试进行身份验证时,会创建一个身份验证对象(用户名密码身份验证令牌或任何身份验证实现,如果是您的自定义身份验证过滤器)。并且 authenticationManager.authenticate(authToken) 将被调用注意,我们可以配置任意数量的 AuthenticationProvider 身份验证方法尝试所有身份验证提供程序并检查任何身份验证提供程序是否支持 authToken/authentication 对象,支持的身份验证提供程序将用于身份验证。并在认证成功的情况下返回 Authentication 对象,否则抛出 AuthenticationException。
创建一个身份验证对象(用户名密码身份验证令牌或身份验证的任何实现,如果是您的自定义身份验证过滤器)。
并且 authenticationManager.authenticate(authToken) 将被调用
请注意,我们可以配置任意数量的 AuthenticationProvider 身份验证方法尝试所有身份验证提供程序并检查任何身份验证提供程序是否支持 authToken/authentication 对象,支持的身份验证提供程序将用于身份验证。并在认证成功的情况下返回 Authentication 对象,否则抛出 AuthenticationException。
如果将创建身份验证成功会话并调用 authenticationSuccessHandler 重定向到配置的目标 URL(默认为 /)
如果身份验证失败的用户成为未经身份验证的用户并且链继续。
SecurityContextHolderAwareRequestFilter,如果您使用它来将 Spring Security 感知 HttpServletRequestWrapper 安装到您的 servlet 容器中 AnonymousAuthenticationFilter 检测 SecurityContextHolder 中是否没有 Authentication 对象,如果没有找到身份验证对象,则创建具有授予权限 ROLE_ANONYMOUS 的身份验证对象 (AnonymousAuthenticationToken)。这里 AnonymousAuthenticationToken 有助于识别未经身份验证的用户后续请求。
调试日志
DEBUG - /app/admin/app-config at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@aeef7b36: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
ExceptionTranslationFilter,捕获任何 Spring Security 异常,以便可以返回 HTTP 错误响应或可以启动适当的 AuthenticationEntryPoint角色授予),它将决定是否允许该请求到达请求的资源,通过与HttpSecurityConfiguration中配置的允许的AntMatchers匹配来做出决定。
考虑例外 401-UnAuthorized 和 403-Forbidden。这些决定将在过滤器链的最后完成
未经身份验证的用户试图访问公共资源 - 允许
未经身份验证的用户试图访问安全资源 - 401-UnAuthorized
经过身份验证的用户尝试访问受限资源(对其角色受限) - 403-Forbidden
注意:用户请求流不仅在上述过滤器中,而且还有其他过滤器也未在此处显示。(ConcurrentSessionFilter
,RequestCacheAwareFilter
,SessionManagementFilter
...)
当您使用自定义时,它会有所不同auth 过滤器而不是 UsernamePasswordAuthenticationFilter
。
如果您配置 JWT auth 过滤器并省略 .formLogin() i.e, UsernamePasswordAuthenticationFilter
,情况将完全不同。
https://i.stack.imgur.com/WxrR7.png
从文档中过滤器的顺序给出为
通道处理过滤器
并发会话过滤器
SecurityContextPersistenceFilter
注销过滤器
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
用户名密码身份验证过滤器
并发会话过滤器
OpenIDAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
并发会话过滤器
摘要认证过滤器
BearerTokenAuthenticationFilter
基本身份验证过滤器
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
匿名身份验证过滤器
会话管理过滤器
异常翻译过滤器
过滤器安全拦截器
切换用户过滤器
您也可以参考
most common way to authenticate a modern web app?
difference between authentication and authorization in context of Spring Security?
Sequence of execution in Spring Security
中只列出了 9 个过滤器?你能评论一下吗?
UsernamePasswordAuthenticationFilter 只用于/login,后面的过滤器不是吗?
不,UsernamePasswordAuthenticationFilter
扩展了AbstractAuthenticationProcessingFilter
,其中包含了一个RequestMatcher
,也就是说你可以定义自己的处理url,这个过滤器只处理与请求url匹配的RequestMatcher
,默认处理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 链中的 fitler 处理。
如果您不配置模式,则默认 RequestMatcher
匹配所有请求,或者您可以配置特定 url (<http pattern="/rest/**"
)。
如果您想了解更多有关 fitler 的信息,我认为您可以在 spring security 中查看源代码。 doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
No qualifying bean of type 'org.springframework.security.web.FilterChainProxy' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}