当前位置: 首页 > 后端技术 > Java

SpringSecurity安全过滤器的工作原理

时间:2023-04-02 00:38:37 Java

通过前面三篇文章,从底层代码的角度分析了SpringSecurity的初始化过程。下面我们就来详细了解一下SpringSecurity的安全过滤器在初始化组装之后是如何工作的。还是跟着图一探究竟吧。下面从底层源码简单分析一下,请求是如何调用到SpringSecurity的安全过滤器的。SpringSecurity安全过滤器的工作原理上一篇已经分析了DelegatinFilterProxy调用FilterChainProxy的源码,所以今天直接从FilterChainProxy的doFilter方法开始:ServletException{Context=request.getAttribute(FILTER_APPLIED)==null;如果(!clearContext){doFilterInternal(请求,响应,链);返回;}尝试{request.setAttribute(FILTER_APPLIED,Boolean.TRUE);doFilterInternal(请求,响应,链);}catch(RequestRejectedExceptionex){this.requestRejectedHandler.handle((HttpServletRequest)请求,(HttpServletResponse)响应,ex);}最后{SecurityContextHolder.clearContext();首先调用getFilters方法:privatevoiddoFilterInternal(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{FirewalledRequestfirewallRequest=this.firewall.getFirewalledRequest((HttpServletRequest)request);HttpServletResponsefirewallResponse=this.firewall.getFirewalledResponse((HttpServletResponse)响应);列表<过滤器>filters=getFilters(firewallRequest);//省略部分源码VirtualFilterChainvirtualFilterChain=newVirtualFilterChain(firewallRequest,chain,filters);出来了,有兴趣的童鞋打开源码看看,很简单:从FilterChainProxy持有的DefaultSecurityFilterChain对象中获取过滤器,这些过滤器就是SpringSecurity初始化过程中组装的SpringSecurity的安全过滤器。然后使用安全过滤器组装一个名为virtualFilterChain的内部对象。这个内部对象主要是持有SpringSecurity的安全过滤器,然后通过调用virtualFilterChain的doFilter方法一个一个调用SpringSecurity的安全过滤器。SpringSecurity安全过滤器SpringSecurity有很多安全过滤器(如下图,SprigBoot默认启动了15个),我们主要关注与用户身份认证和身份验证密切相关,或者在项目中比较常见的过滤器。暂时绕过或跳过其他过滤器。1.WebAsyncManagerIntegrationFilterweb异步集成过滤器,创建一个SecurityContextCallableProcessingInterceptor并注册到WebAsyncManager中。详细功能省略。2、SecurityContextPersistenceFilter在认证后保存包含认证信息的SecurityContextHolder(保存在SecurityContextRepository中,一般是一个session),在每次请求发起认证之前,从SecurityContextRepository中获取并放入SecurityContextHolder中。SecurityContextPersistenceFilter是SpringSecurity的第二个安全过滤器,在所有认证过滤器之前执行。SecurityContextPersistenceFilter首先从会话中获取SecurityContext(默认情况下)。如果会话中没有SecurityContext,则创建一个空的SecurityContext,然后将SecurityContext存储在SecurityContextHolder中。默认情况下,SecurityContextHolder通过ThreadLocalSecurityContextHolderStrategy(即使用ThreadLocal)存储SecurityContext。最后,请求执行完成后,保存SecurityContextHolder持有的SecurityContext(默认保存在session中),清除SecurityContextHolder。这样,通过SecurityContextPersistenceFilter过滤器,SpringSecurity可以在其他安全过滤器执行之前,从session中获取到当前用户的认证信息,所以在后续的用户认证操作执行之前,如果当前用户已经完成认证,则可以保证。获取到当前用户的认证信息,才能通过后续的用户认证流程!!!3.HeaderWirterFilter写入Response头信息,以实现对HttpResponse的相应控制。HeaderWirterFilter在初始化的时候,会注册如下几个HeadWriters:这些HeadWriters会帮助我们对HttpResponseHeader进行一些默认设置。例如,CacheControlHeadersWriter将执行以下操作:privatestaticList

createHeaders(){List
headers=newArrayList<>(3);headers.add(newHeader(CACHE_CONTROL,"no-cache,no-store,max-age=0,must-revalidate"));headers.add(newHeader(PRAGMA,"no-cache"));headers.add(newHeader(EXPIRES,"0"));返回标题;}一般来说,我们的应用程序需要这样的设置。如果不使用SpringSecurity,我们的应用需要手动设置。如果使用了SpringSecurity,则不需要这样做。4、CsrfFilter跨域请求过滤器,主要作用是防止跨域攻击。5.LogoutFilter处理注销请求并导航到注销页面。6.UsernamePasswordAuthenticationFilter这是SpringSecurity用户认证的默认实现。他有一个父类叫AbstractAuthenticationProcessingFilter,doFilter方法是在父类中实现的。它只对post方法的/login请求生效,所以如果我们要使用这个过滤器,应用的login必须是post方法的/login请求。SpringSecurity的登录认证过程就是通过这个过滤器实现的,比较复杂,篇幅关系今天就不展开了。今天的主要目标是搞清楚它的作用。首先验证当前请求是否需要登录认证。如前所述,如果请求不是post/login,则不会进行登录认证。根据从身份验证请求中获得的用户名和密码创建一个UsernamePasswordAuthenticationToken对象。认证过程实际上就是验证用户名和密码的过程。SpringSecurity有一个默认的用户名和密码认证实现,在系统启动时为用户user生成一个UUID,并打印在控制台上。用户名和密码就是这个UUID完成认证。我们可以实现自己的认证,比如通过数据库的用户名和密码完成认证。如果认证通过,则将认证信息存储在SecurityContext中。如果认证失败(例如用户名密码错误等),SecurityContext会被清空,并记录相关异常。7.DefaultLoginPageGeneratingFilter为/login请求生成登录页面。8.DefaultLogoutPageGeneratingFilter导航到注销页面。9.BasicAuthenticationFilter使用BasicAuthentication对请求进行认证。BasiAuthentication是指请求头包含:Authorization:BasicQWxhZGRpbjpvcGVuIHNlc2FtZQ==其中Basic后面是base64编码的密码信息。本人了解不多,之前也没有用过这种认证方式,略过。10、RequestCacheAwareFilter的主要作用是缓存当前的请求信息,这样如果因为鉴权失败导致请求无法执行,后续鉴权通过后会重新发起请求。11.SecurityContextHolderAwareRequestFilter用于对请求做一层包装,以支持Servlet中安全相关的API调用。12、AnonymousAuthenticationFilter匿名过滤器,从SecurityContextHolder中获取认证信息。如果经过前面的SecurityContextPersistenceFilter和UsernamePasswordAuthenticationFilter处理后,SecurityContextHolder中仍然没有认证信息,则说明当前请求没有通过SpringSecurity的安全认证,则创建一个匿名认证信息存储在SecurityContextHolder中。13.SessionManagementFilters会话管理过滤器主要用于防止会话固化攻击,实现登录并发控制功能。14.ExceptionTranslationFilter实现异常控制的安全过滤器。请注意,过滤器在安全过滤器链执行完成并返回后处理异常。所以下面第14、15个过滤器执行过程中出现的异常,也可以被这个过滤器捕获并处理。它主要处理SpringSecurity中的两类异常:AuthenticationException、AccessDeniedException,并将异常交给对应的异常处理器进行处理。比如AccessDeniedException异常处理器,通常会通过response返回前端403错误。15.FilterSecurityInterceptorSpringSecurity的主要安全过滤器的实现已经接近尾声,但是这个FilterSecurityInterceptor是重头戏。FilterSecurityInterceptor主要完成以下任务:解析当前请求的安全配置,主要包括:permitAll、authenticated、denyAll、anonymous等。从SecurityContextHolder获取安全认证信息。根据当前请求的安全配置信息和当前用户的安全认证信息进行认证投票。其实就是判断获取到的当前用户的安全认证信息是否满足配置的安全认证要求。异常。FilterSecurityInterceptor之所以是SpringSecurity安全过滤器的重头戏,是因为根据项目的具体需求,对不同请求配置的安全认证标准的解析,都是由FilterSecurityInterceptor完成的。分析后,检查当前用户是否满足请求要求的安全要求。该要求也是由FilterSecurityInterceptor完成的。如果当前用户认证信息不满足当前请求的安全要求,比如匿名用户访问/helloworld,/helloworld配置为认证,FilterSecurityInterceptor会抛出accesssdeny异常,最后前台页面会收到一个ExceptionTranslationFilter处理后的403错误信息。总结一下SpringSecurity主要安全过滤器的基本工作原理。其实每个安全过滤器的底层工作细节都可以单独写成一篇文章进行代码层面的分析。但一般来说,对于比较复杂的知识,在学习之初分析的过于详细,会影响对整体框架的理解。SpringSecurity相对负责,所以适合这个原则。上一篇SpringSecurity的初始化过程(三)