前面通过三篇文章,从底层代码的角度分析了SpringSecurity的初始化过程。
接下来我们就要具体看一下,Spring Security的安全过滤器初始化、装配好之后,到底是怎么工作的。
还是按图索骥
下面我们简单从底层源码分析一下,请求是怎么调用到Spring Security的安全过滤器的。
Spring Security安全过滤器工作原理
前面文章已经分析过DelegatinFilterProxy调用到FilterChainProxy的源码,所以我们今天直接从FilterChainProxy的doFilter方法开始:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
直接看doInternalFilter方法。首先调用getFilters方法:
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(firewallRequest);
//省略部分源码
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
这个getFilters方法的源码就不贴出了,有兴趣的童鞋打开源码看一下,非常简单:从FilterChainProxy持有的DefaultSecurityFilterChain对象中获取到filters,这个filters就是Spring Security初始化过程中装配好的Spring Security的各安全过滤器。
之后用安全过滤器组装一个叫virtualFilterChain的内部对象,这个内部对象主要就是为了持有Spring Security的安全过滤器的,然后通过调用virtualFilterChain的doFilter方法,逐个调用Spring Security的安全过滤器。
Spring Security安全过滤器
Spring Security有不少安全过滤器(下图,SprigBoot默认启动的就有15个),我们主要关注与用户身份认证及鉴权密切相关的、或者项目中比较常见的过滤器,其他过滤器暂时略过或一笔带过。
1.WebAsyncManagerIntegrationFilter
web异步集成过滤器,创建SecurityContextCallableProcessingInterceptor并注册到WebAsyncManager中。
详细作用略。
2.SecurityContextPersistenceFilter
将包含认证信息的SecurityContextHolder在认证之后保存起来(保存在SecurityContextRepository中,一般情况下是session),并且在每次请求发起、认证发生之前从SecurityContextRepository获取出来放置到SecurityContextHolder中。
SecurityContextPersistenceFilter是Spring Security的第2个安全过滤器,在所有认证过滤器之前执行。
SecurityContextPersistenceFilter首先从session中(默认情况下)获取SecurityContext,如果session中没有SecurityContext则创建一个空的SecurityContext,之后将SecurityContext存入SecurityContextHolder。
默认情况下SecurityContextHolder通过ThreadLocalSecurityContextHolderStrategy(也就是使用ThreadLocal)存储SecurityContext。
最后,在请求执行完成之后,将SecurityContextHolder持有的SecurityContext保存起来(默认存储在session中),并清空SecurityContextHolder。
这样,通过SecurityContextPersistenceFilter过滤器,Spring Security就可以在其他安全过滤器执行之前,从session中获取当前用户的认证信息,因而在后续用户认证操作执行之前、如果当前用户已经完成了认证、则可以确保能够获取到当前用户的认证信息,因此能够通过后续的用户认证过程!!!
3.HeaderWirterFilter
写Response头信息以便达到对Http Response的相应控制。
HeaderWirterFilter初始化的时候会注册如下HeadWriter:
这些HeadWriter会帮助我们对Http Response Header做一些默认设置,比如CacheControlHeadersWriter会执行如下操作:
private static List<Header> createHeaders() {
List<Header> headers = new ArrayList<>(3);
headers.add(new Header(CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"));
headers.add(new Header(PRAGMA, "no-cache"));
headers.add(new Header(EXPIRES, "0"));
return headers;
}
一般情况下,我们的应用需要这样的设置,不使用Spring Security的话我们的应用需要手动设置,使用了Spring Security的话,就不需要多此一举了。
4.CsrfFilter
跨域请求过滤器,主要作用是防跨域攻击。
5.LogoutFilter
处理Logout请求,并导航到logout页面。
6.UsernamePasswordAuthenticationFilter
这是Spring Security用户认证的默认实现。
他有一个叫AbstractAuthenticationProcessingFilter的父类,doFilter方法就是在父类实现的。
只针对post方法的/login请求生效,所以如果我们要使用该过滤器的话,应用的登录必须是post方法的/login请求。
Spring Security的登录认证过程就是通过这个过滤器实现的,相对还比较复杂,篇幅关系今天就不展开了,今天的主要目标是要搞清楚他的作用。
首先验证当前请求是否需要登录认证,如前所述,如果请求不是post的/login的话,则不做登录认证。
从request中获取的username,password创建UsernamePasswordAuthenticationToken对象进行认证。
认证的过程其实就是我们验证用户名、密码的过程,SpringSecurity有一个默认的用户名、密码认证实现,是在系统启动过程中为用户user生成UUID并打印在控制台、通过用户名user、密码为该UUID完成认证。我们可以实现自己的认证,比如通过数据库的用户名、密码方式完成认证。
认证通过则将认证信息存储在SecurityContext中,如果认证失败(比如用户名密码错误等)则清空SecurityContext并记录相关异常。
7.DefaultLoginPageGeneratingFilter
为/login请求生成登录页面。
8.DefaultLogoutPageGeneratingFilter
导航到登出页面。
9.BasicAuthenticationFilter
对于采用Basic Authentication的请求执行认证。Basi Authentication指的是请求头中包含:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
其中Basic后是采用base64编码的密码信息。
不太了解、没有使用过这种认证方式,略过。
10.RequestCacheAwareFilter
主要作用是缓存当前请求信息,以便在由于认证未通过无法执行该请求的情况下,后续认证通过后重新发起该请求。
11.SecurityContextHolderAwareRequestFilter
用于对request做一层包装,以便支持Servlet中安全相关的API调用。
12.AnonymousAuthenticationFilter
匿名过滤器,从SecurityContextHolder获取认证信息,如果经过了前面的SecurityContextPersistenceFilter、以及UsernamePasswordAuthenticationFilter处理后,SecurityContextHolder中还是没有认证信息的话,说明当前请求并没有通过Spring Security的安全认证,则创建一个匿名认证信息存储在SecurityContextHolder中。
13.SessionManagementFilter
session管理的过滤器,主要为了防止session固化攻击、以及实现登录并发控制功能。
14.ExceptionTranslationFilter
实现异常控制的安全过滤器,注意该过滤器对异常的处理是安全过滤器链执行完成、返回之后。所以,下面的第14个、第15个过滤器执行过程中的异常,也能被本过滤器捕获并处理。
主要处理Spring Security的两类异常:
- AuthenticationException
- AccessDeniedException
并将异常交给相应的异常处理器进行处理,比如AccessDeniedException异常处理器通常会通过response返回前端403错误。
15.FilterSecurityInterceptor
Spring Security主要安全过滤器的执行已经接近尾声,但是这个FilterSecurityInterceptor是重头戏。
FilterSecurityInterceptor主要完成如下工作:
- 解析当前请求的安全配置,主要包括:permitAll、authenticated、denyAll、anonymous等。
- 从SecurityContextHolder获取安全认证信息。
- 根据当前请求的安全配置信息及当前用户的安全认证信息进行认证投票,其实就是判断获取到的当前用户的安全认证信息是否符合配置的安全认证需求,符合的话则通过认证,不符合的话抛出accesssdeny异常。
之所以说FilterSecurityInterceptor是Spring Security安全过滤器的重头戏,是因为我们根据项目的具体需求、针对不同请求配置好的安全认证标准的解析是FilterSecurityInterceptor完成的,解析之后检查当前用户是否满足请求所需要的安全要求也是FilterSecurityInterceptor完成的。
如果当前用户认证信息不满足当前请求的安全需求,比如匿名用户访问/helloworld、/helloworld配置为authenticated的话,FilterSecurityInterceptor会抛出accesssdeny异常,最终经ExceptionTranslationFilter处理之后前台页面会收到403错误信息。
总结
Spring security主要安全过滤器的基本工作原理分析完毕,其实每一个安全过滤器的底层工作细节都可以单独写一篇文章进行代码级的分析。但是一般来讲,对于相对复杂一点的知识,入门学习的时候太过细节的分析会影响到对整体框架的理解,Spring Security相对来讲比较负责,因此适合这一原则。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。