原文连接:https://blog.gaoyuexiang.cn/2020/06/07/spring-security-authentication/,内容无差异。java
本文介绍 Spring Security 的身份认证的内容,研究 Spring Security 自带的身份认证方式和添加本身的身份认证方式的方法。spring
在上一篇文章中,咱们了解到了 Spring Security 会将 DelegatingFilterProxy
插入到 Servlet Filter Chain 中,而后将要过滤的请求经过 FilterChainProxy
代理给匹配的 SecurityFilterChain
;这些 SecurityFilterChain
中包含着真正作安全相关工做的 Filter
。数据库
这些 Filter
中的一部分,他们的职责就是进行身份验证,好比 UsernamePasswordAuthenticationFilter
;而他们中的大多数都有一个共同的父类 AbstractAuthenticationProcessingFilter
。segmentfault
这个类是不少身份认证的 Filter 的父类,它已经实现了 doFilter
方法,流程以下:安全
本文不涉及其中的 sessionStrategy 部分
doFilter
已经帮咱们搭好了这个流程,咱们只须要关心其中的几个被调用的方法(红绿蓝三个颜色框)就能够了。session
这是一个抽象方法。子类实现的时候,须要从 HttpServletRequest
中获取须要的信息,构建出一个 Authentication
实例,而后调用父类中的 AuthenticationManager.authenticate()
方法,对 Authentication
对象进行认证。mvc
这个方法已经被实现,子类也能够选择重写。根据父类的实现,这个方法将完成一下步骤:ide
SecurityContextHolder
RememberMeService
中的信息(默认使用 NullRememberMeService
)AuthenticationFailureHandler.onAuthenticationFailure()
方法默认使用的 AuthenticationFailureHandler
是 SimpleUrlAuthenticationFailureHandler
,它的逻辑是:spring-boot
若是没有配置 defaultFailureUrl
(默认没有)网站
根据配置的布尔值 forwardToDestination
(默认为 false
)判断
defaultFailureUrl
defaultFailureUrl
与 unsuccessfulAuthentication
方法同样,这个方法也已经实现,而且能够被重写,但其中的逻辑却刚好相反:
attemptAuthentication
返回的 Authentication
对象保存到 SecurityContextHolder
中RememberMeService
中InteractiveAuthenticationSuccessEvent
事件,这样能够被配置的 EventListener
处理AuthenticationSuccessHandler.onAuthenticationSuccess()
方法默认使用的 AuthenticationSuccessHandler
是 SavedRequestAwareAuthenticationSuccessHandler
,其实现就是一次重定向。咱们能够看看它重定向到哪里:
当配置了 alwaysUseDefaultTargetUrl
或指定了 targetUrlParameter
且此参数存在的时候
alwaysUseDefaultTargetUrl
则重定向到 defaultTargetUrl
,默认是 /
targetUrlParameter
(好比 redirect_uri 之类的比较常见的参数),则重定向到这个路径Referer
,则重定向到这个地址/
从 RequestCache
中找到了保存的请求
关于RequestCache
:想象你正在访问一个须要认证的资源,这个时候网站会把你重定向到登录页面;在你登录成功后,又会重定向回刚才的资源。RequestCache
就是为了保存登录以前的请求而设计的。在这里,默认使用基于 session 的实现。
在 AbstractAuthenticationProcessingFilter
中保存了一个 AuthenticationManager
,它会在子类的 attemptAuthentication
方法中被使用。其职责是对 Filter
建立的 Authentication
对象进行身份验证,好比查询数据库匹配用户名密码、携带的 token 是否合法等。
这是 AuthenticationManager
经常使用的实现。它没有实现任何认证逻辑,而是管理了一些 AuthenticationProvider
,经过这些 provider 来实现真正的认证功能。
每一个 AuthenticationProvider
实现一种特定类型的身份认证方式,好比用户名密码登录、OIDC 登录等。他们能够经过 Authentication
的具体类型来判断是否支持这种 Authentication
须要的认证方式。
除了 AbstractAuthenticationProcessFilter
,还有一些进行身份验证的 Filter
,它们并无继承这个类,而是基于 OncePerRequestFilter
本身实现了一套逻辑。这些 Filter
包括 AuthenticationFilter
、BasicAuthenticationFilter
、OAuth2AuthorizationCodeGrantFilter
等等。
因为它们再也不是 AbstractAuthenticationProcessFilter
,因此不会再被要求使用 AuthenticationManager
。尽管这样,当咱们选择使用 OncePerReuqestFilter
来实现自定义的身份认证时,仍然能够考虑使用 AuthenticationManager
这种方式。
我的以为
AuthenticationManager
还算是个不错的设计,由于作到了职责分离。
甚至还有更加放飞自个人 DigestAuthenticationFilter
,直接继承 GenericFilterBean
,在实现上也是我行我素,这里就不探究了。
这个 Filter 不是用来进行身份验证的,而是用来处理认证受权过程当中产生的异常的。它能够处理 AuthenticationException
和 AccessDeniedException
,分别表示认证失败和受权失败。这篇文章只关心如何处理 AuthenticationException
。
可是这个 Filter
默认被安排在 SecurityFilterChain
的倒数第二位,因此前面的 Filter
抛出的异常并不能被它捕获。但自定义的 Filter
能够加到它后面,这样就能够利用它来处理这两种异常。
最后一位是FilterSecurityInterceptor
,可能会抛出AccessDeniedException
。
ExceptionTranslationFilter
对 AuthenticationException
的处理分三步:
SecurityContextHolder
中的身份信息RequestCache
中(用途能够回顾一下 successfulAuthentication 方法)AuthenticationEntryPoint.commence()
方法其中的 AuthenticationEntryPoint
具体实例取决于你的配置,默认会用到 BasicAuthenticationEntryPoint
。这个接口的职责就是经过 WWW-Authenticate
header 告诉客户端使用哪一种方式进行身份验证。
对于 AuthenticationException
和 AccessDeniedException
以外的异常,ExceptionTranslationFilter
会将其转换成 ServletException
或 RuntimeException
抛出。
若是想要处理这些异常,须要本身添加 Filter
实现。
当咱们启动 Spring 应用以后,会在日志里看到打印全部配置的 FilterChainProxy
。
默认状况下,咱们会看到这样的一条链:
这是引入 spring-boot-starter-security
以后自动配置的 FilterChainProxy
,在引入更多的 security 相关的依赖和编写了相关配置以后,这个 filter chain 也会相应变化。
接下来,咱们以 UsernamePawwrodAuthenticationFilter
和 BasicAuthenticationFilter
为例,看看他们是如何实现身份认证的。
UsernamePasswordAuthenticationFilter
是一个 AbstractAuthenticationProcessingFilter
的子类,实现了 attemptAuthentication
方法,没有重写其余方法。因此用户认证成功后,会被重定向到一个地址,具体逻辑参考上面的 successfulAuthentication 方法。
attemptAuthentication
方法会从 HttpServletRequest.getParameter()
方法中获取用户名密码,从而进行身份验证。具体从哪里获取用户名密码,则能够被子类经过重写 obtainUsername()
和 obtainPassword()
方法修改。
以后,UsernamePasswordAuthenticationFilter
会构建出一个 UsernameAuthenticationToken
,交给 AuthenticationManager
进行认证。
这是 UsernamePasswordAuthenticationFilter
对应的 AuthenticationProvider
,负责对 UsernameAuthenticationToken
进行认证。
它使用一个 UserDetailsService
来加载用户信息,使用 PasswordEncoder
来匹配用户的密码。
这两个接口具体使用哪个实现,取决于具体的配置。好比 UserDetailsService
就有 in memory 和 JDBC 的实现。
UsernamePasswordAuthenticationFilter
是用于单独处理登陆的Filter
,它不是用来在请求业务 API 时进行身份认证的Filter
。事实上,全部继承了
AbstractAuthenticationProcessFilter
但没有重写successfulAuthentication
方法的Filter
都是这样的,它们会在登录成功后重定向到登陆前的地址或默认的地址。这也符合它的语义:进行身份认证流程,而不是业务请求的一部分。
与 UsernamePasswordAuthenticationFilter
不一样,BasicAuthenticationFilter
没有继承 AbstractAuthenticationProcessingFilter
,而是直接继承 OncePerRequestFilter
。由于它是被使用在请求业务 API 的请求上,而不是进行身份认证流程。
BasicAuthenticationFilter
的实现并不复杂,无非是从 Authorization
header 中取出用户名密码,而后建立出 UsernameAuthenticationToken
,接着调用 AuthenticationManager.authenticate()
方法。
之因此它也会使用AuthenticationManager
,应该是出于复用的考虑。这样它就可使用和UsernamePasswordAuthenticationFilter
同样的AuthenticationProvider
。
它与 UsernamePasswordAuthenticationFilter
的区别在于认证以后的行为。
不管认证成功与否,BasicAuthenticationFilter
都不会作出重定向的响应。
BasicAuthenticationEntryPoint
返回 401 响应前面介绍了两种不一样的 Filter
实现,以及它们被使用的场景,如今咱们知道了该选择哪种方式去实现自定义的 Filter
。可是,如何把它们加入到 SecurityFilterChain
中去处理身份认证呢?
咱们若是须要任何对 SecurityFilterChain
的配置,都须要扩展 WebSecurityConfigurerAdapter
,实现本身的一个配置类。每建立这样的一个实现,都会建立一个 SecurityFilterChain
加入到 FilterChainProxy
中。
咱们在前一篇文章提到过,FilterChainProxy
须要根据 url 来判断选择哪个 SecurityFilterChain
。咱们须要将这个配置写到这个实现类中,好比:
@Override protected void configure(HttpSecurity http) throws Exception { http.requestMatchers(matcher -> matcher.mvcMatchers("/hello")); }
这样,FilterChainProxy
就知道了对 /hello
的请求须要使用这个 SecurityFilterChain
。
如今有了对应的 SecurityFilterChain
,咱们就能够将自定义的 Filter
加入到这个 chain 中:
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilter(HelloFilter.class); }
addFilter
方法也有一些变体,能够控制 Filter
在 chain 中的位置,这里就不赘述了。
与 Filter
同样,AuthenticationProvider
也是被安排到单独的 FilterChainProxy
中的,而且须要本身配置。若是你的自定义 Filter
须要 AuthenticationProvider
的话,一样须要配置:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(HelloAuthenticationProvider.class); }
这篇文章比较详细的梳理了 AbstractAuthenticationProcessingFilter
及其子类 UsernamePasswordAuthenticationFilter
的实现和 BasicAuthenticationFilter
的实现,了解了须要实现自定义身份验证的 Filter
时应该选择哪一种方式:
AbstractAuthenticationProcessFilter
OncePerRequestFilter
,彻底控制认证的流程
固然,这不是一个强制的限制,你仍然能够经过重写
AbstractAuthenticationProcessFilter.successfulAuthentication()
方法来修改重定向的行为。
另外,也了解到了实现完 Filter
后,须要实现 WebSecurityConfigurerAdapter
,将 Filter
加入到 SecurityFilterChain
中。
👉 查看系列文章