原理讲解
接上回分解,上回我们聊到了认证(一):基于表单登录的认证模式,正所谓:“知其然,然后知其所以然”;就让我们来一探究竟,瞅瞅 Spring Security
到底是怎么实现表单登录的。
首先我们先来回顾一下上篇文章我们制作的 Spring Security
的表单认证的流程图:
从流程图中我们可以简单的了解到,整个认证流程大致上分为3个模块:
-
登录信息的封装
-
认证
-
收尾处理(成功&失败处理)
核心模块为 认证模块
,下面就来看看认证模块 AuthenticationManager
的相关类图:
该图可以分2块来看,分别是左边负责掌控全局的"大哥",以及右边勤勤恳恳的"小弟们"。
总所周知,“大哥"都不需要亲历亲为的,所以"大哥” AuthenticationManager
认证管理接口,只定义了认证方法 authenticate()
,具体咋实现就让小弟们去搞吧~~
“二当家” ProviderManager
为认证管理类,实现了 AuthenticationManager
(二当家肯定要听大哥的话),并在认证方法 authenticate()
中将身份认证委托给具有认证资格的 AuthenticationProvider
(真正干活的小弟们);同时
ProviderManaer
有一个成员变量 List<AuthenticationProvider> providers
用以存储了所有具体执行认证的"小弟们"。
接下来介绍一下右边勤勤恳恳的"小弟们",首先是AuthenticationProvider
认证接口类,其定义了身份认证方法authenticate()
;这个也比较好理解;你怎么证明自己是我的"小弟"呢?当然是得入我门为我干活拉!AuthenticationProvider
接口就是起这个作用。
AbstractUserDetailAuthenticationProvider
为认证抽象类,实现了接口AuthenticationProvider
,同时还定义了抽象方法retrieveUser()
用于从数据库中获取用户信息,以及additionalAuthenticationChecks()
做身份认证;这块可能会犯迷糊,为啥子这个"小弟"还是个抽象类呢?不必慌张,其实只是为了一些功能的复用。
DaoAuthenticationProvider
认证类继承于AbstractUserDetailAuthenticationProvider
抽象认证类,实现了上面提到的2个抽象方法retrieveUser和additionalAuthenticationChecks
;并自定义了一些成员变量:private UserDetailsService userDetailsService;
用以用户信息查询,以及private PasswordEncoder passwordEncoder
用作密码的加密认证。
源码解析
在大致了解了原理之后,就开始了我们的阅读源码之旅拉;分两个模块来看,分别是:登录信息的封装
以及 认证
。
登录信息的封装
登录信息的封装是指将前端传递的username和password
封装成 UsernamePasswordAuthenticationToken
。
UsernamePasswordAuthenticationFilter.class的 attemptAuthentication()
方法
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 将http请求的Request带的认证参数:username、password转换为认证的token对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 设置一些详细信息, 诸如发送请求的ip等...
this.setDetails(request, authRequest);
// 调用AuthenticationManager的authenticate方法 执行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
attemptAuthentication()
方法做的事情很简单,主要是将登录信息 username和password
封装成 UsernamePasswordAuthenticationToken
。那么这个Token
到底是起什么作用呢?其实也很简单,主要是用于后续认证的时候,寻找匹配的认证处理器,例如表单登录的 UsernamePasswordAuthenticationToken
会唯一匹配相应的认证Provider
。
认证
从上面我们也可以看到,在将登录信息封装成Token
后,就调用了 AuthenticationManager
的 authenticate()
方法执行认证操作;因 AuthenticationManager
是一个接口,我们来分析它的实现类 ProviderManager
。
ProviderManager.class的authenticate()
方法
public Authentication authenticate(Authentication authen