技术分享
🔐Spring Security(2):过滤器链
00 分钟
2024-7-24
2024-7-25
type
status
date
Jul 25, 2024 01:28 AM
slug
summary
category
tags
password
icon

核心过滤器链

SpringSecurity的本质其实就是一个过滤器链,内部包含了提供各种功能的过滤器。
图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
  • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
  • FilterSecurityInterceptor:负责权限校验的过滤器。
我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。
notion image
先大致介绍下每个过滤器的作用(其中加粗的可以被认为是Spring Security的核心过滤器):
  • SecurityContextPersistenceFilter 两个主要职责:请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder
  • HeaderWriterFilter(文档中并未介绍,非核心过滤器)用来给http响应添加一些Header,比如X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options.
  • CsrfFilter 在spring4这个版本中被默认开启的一个过滤器,用于防止csrf攻击,了解前后端分离的人一定不会对这个攻击方式感到陌生,前后端使用json交互需要注意的一个问题。
  • LogoutFilter 顾名思义,处理注销的过滤器
  • UsernamePasswordAuthenticationFilter:用户密码认证的过滤器,表单提交了username和password,被封装成token进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。
  • RequestCacheAwareFilter(文档中并未介绍,非核心过滤器)内部维护了一个RequestCache,用于缓存request请求
  • SecurityContextHolderAwareRequestFilter 此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
  • AnonymousAuthenticationFilter:匿名身份过滤器,这个过滤器个人认为很重要,需要将它与UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
  • SessionManagementFilter 和session相关的过滤器,内部维护了一个SessionAuthenticationStrategy,两者组合使用,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
  • ExceptionTranslationFilter:异常翻译过滤器,还是比较形象的,这个过滤器本身不处理异常,而是将认证过程中出现的异常交给内部维护的一些类去处理,具体是那些类下面详细介绍
  • FilterSecurityInterceptor:这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。

SecurityContextPersistenceFilter

试想一下,如果我们不使用Spring Security,如果保存用户信息呢,大多数情况下会考虑使用Session对吧?在Spring Security中也是如此,用户在登录过一次之后,后续的访问便是通过sessionId来识别,从而认为用户已经被认证。用户的具体信息便存在SecurityContextHolder;认证相关的信息是如何被存放到其中的,便是通过SecurityContextPersistenceFilter。前面也提到了,SecurityContextPersistenceFilter的两个主要作用便是请求来临时,创建SecurityContext安全上下文信息和请求结束时清空SecurityContextHolder。顺带提一下:微服务的一个设计理念需要实现服务通信的无状态,而http协议中的无状态意味着不允许存在session,这可以通过setAllowSessionCreation(false) 实现,这并不意味着SecurityContextPersistenceFilter变得无用,因为它还需要负责清除用户信息。在Spring Security中,虽然安全上下文信息被存储于Session中,但我们在实际使用中不应该直接操作Session,而应当使用SecurityContextHolder
过滤器一般负责核心的处理流程,而具体的业务实现,通常交给其中聚合的其他实体类,这在Filter的设计中很常见,同时也符合职责分离模式。例如存储安全上下文和读取安全上下文的工作完全委托给了HttpSessionSecurityContextRepository去处理,而这个类中也有几个方法可以稍微解读下,方便我们理解内部的工作流程
SecurityContextPersistenceFilter和HttpSessionSecurityContextRepository配合使用,构成了Spring Security整个调用链路的入口,为什么将它放在最开始的地方也是显而易见的,后续的过滤器中大概率会依赖Session信息和安全上下文信息。

UsernamePasswordAuthenticationFilter

表单认证是最常用的一个认证方式,一个最直观的业务场景便是允许用户在表单中输入用户名和密码进行登录,而这背后的UsernamePasswordAuthenticationFilter,在整个Spring Security的认证体系中则扮演着至关重要的角色。
notion image
上述的时序图,可以看出UsernamePasswordAuthenticationFilter主要肩负起了调用身份认证器,校验身份的作用,到这里,其实Spring Security的基本流程就已经走通了。
UsernamePasswordAuthenticationFilter本身的代码只包含了上述这么一个方法,非常简略,而在其父类AbstractAuthenticationProcessingFilter中包含了大量的细节,值得我们分析:
整个流程理解起来也并不难,主要就是内部调用了authenticationManager完成认证,根据认证结果执行successfulAuthentication或者unsuccessfulAuthentication,无论成功失败,一般的实现都是转发或者重定向等处理,不再细究AuthenticationSuccessHandlerAuthenticationFailureHandler

AnonymousAuthenticationFilter

可能有人会想:匿名了还有身份?我自己对于Anonymous匿名身份的理解是Spirng Security为了整体逻辑的统一性,即使是未通过认证的用户,也给予了一个匿名身份。而AnonymousAuthenticationFilter该过滤器的位置也是非常的科学的,它位于常用的身份认证过滤器(如UsernamePasswordAuthenticationFilterBasicAuthenticationFilterRememberMeAuthenticationFilter)之后,意味着只有在上述身份过滤器执行完毕后,SecurityContext依旧没有用户信息,AnonymousAuthenticationFilter该过滤器才会有意义—-基于用户一个匿名身份。
其实对比AnonymousAuthenticationFilterUsernamePasswordAuthenticationFilter就可以发现一些门道了,UsernamePasswordAuthenticationToken对应AnonymousAuthenticationToken,他们都是Authentication的实现类,而Authentication则是被SecurityContextHolder(SecurityContext)持有的,一切都被串联在了一起。

ExceptionTranslationFilter

  • ExceptionTranslationFilter异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常,将其转化,顾名思义,转化以意味本身并不处理。一般其只处理两大类异常:AccessDeniedException访问异常和AuthenticationException认证异常。
  • 这个过滤器非常重要,因为它将Java中的异常和HTTP的响应连接在了一起,这样在处理异常时,我们不用考虑密码错误该跳到什么页面,账号锁定该如何,只需要关注自己的业务逻辑,抛出相应的异常便可。如果该过滤器检测到AuthenticationException,则将会交给内部
  • AuthenticationEntryPoint去处理,如果检测到AccessDeniedException,需要先判断当前用户是不是匿名用户,如果是匿名访问,则和前面一样运行AuthenticationEntryPoint,否则会委托给AccessDeniedHandler去处理,而AccessDeniedHandler的默认实现,是AccessDeniedHandlerImpl。所以ExceptionTranslationFilter内部的AuthenticationEntryPoint是至关重要的,顾名思义:认证的入口点。
剩下的便是要搞懂AuthenticationEntryPointAccessDeniedHandler就可以了。
notion image
选择了几个常用的登录端点,以其中第一个为例来介绍,看名字就能猜到是认证失败之后,让用户跳转到登录页面。还记得我们一开始怎么配置表单登录页面的吗?
我们顺着formLogin返回的FormLoginConfigurer往下找,看看能发现什么,最终在FormLoginConfigurer的父类AbstractAuthenticationFilterConfigurer中有了不小的收获:
  • 因此得出了结论,formLogin()配置了之后最起码做了两件事,其一,为UsernamePasswordAuthenticationFilter设置了相关的配置,其二配置了AuthenticationEntryPoint
  • 登录端点还有Http401AuthenticationEntryPointHttp403ForbiddenEntryPoint这些都是很简单的实现,有时候我们访问受限页面,又没有配置登录,就看到了一个空荡荡的默认错误页面,上面显示着401,403,就是这两个入口起了作用。
  • 还剩下一个AccessDeniedHandler访问决策器未被讲解,简单提一下:AccessDeniedHandlerImpl这个默认实现类会根据errorPage和状态码来判断,最终决定跳转的页面

FilterSecurityInterceptor

  • 想想整个认证安全控制流程还缺了什么?我们已经有了认证,有了请求的封装,有了Session的关联…还缺一个:由什么控制哪些资源是受限的,这些受限的资源需要什么权限,需要什么角色…这一切和访问控制相关的操作,都是由FilterSecurityInterceptor完成的。
  • FilterSecurityInterceptor的工作流程用笔者的理解可以理解如下:FilterSecurityInterceptorSecurityContextHolder中获取Authentication对象,然后比对用户拥有的权限和资源所需的权限。前者可以通过Authentication对象直接获得,而后者则需要引入我们之前一直未提到过的两个类:SecurityMetadataSourceAccessDecisionManager。理解清楚决策管理器的整个创建流程和SecurityMetadataSource的作用需要花很大一笔功夫,这里,暂时只介绍其大概的作用。
  • 在JavaConfig的配置中,我们通常如下配置路径的访问控制:
在ObjectPostProcessor的泛型中看到了FilterSecurityInterceptor,目前并没有太多机会需要修改FilterSecurityInterceptor的配置。
 

评论
Loading...