概要
前面一节,通过简单配置即可实现SpringSecurity表单认证功能,而今天这一节将通过阅读源码的形式来学习SpringSecurity是如何实现这些功能, 前方高能预警,本篇分析源码篇幅较长
。
过滤器链
前面我说过SpringSecurity是基于过滤器链的形式,那么我解析将会介绍一下具体有哪些过滤器。
Filter Class | 介绍 |
---|---|
SecurityContextPersistenceFilter | 判断当前用户是否登录 |
CrsfFilter | 用于防止csrf攻击 |
LogoutFilter | 处理注销请求 |
UsernamePasswordAuthenticationFilter | 处理表单登录的请求(也是我们今天的主角) |
BasicAuthenticationFilter | 处理http basic认证的请求 |
由于过滤器链中的过滤器实在太多,我没有一一列举,调了几个比较重要的介绍一下。
通过上面我们知道SpringSecurity对于表单登录的认证请求是交给了UsernamePasswordAuthenticationFilter处理的,那么具体的认证流程如下:
从上图可知,UsernamePasswordAuthenticationFilter
继承于抽象类AbstractAuthenticationProcessingFilter
。
具体认证是:
- 进入doFilter方法,判断是否要认证,如果需要认证则进入attemptAuthentication方法,如果不需要直接结束
- attemptAuthentication方法中根据username跟password构造一个UsernamePasswordAuthenticationToken对象(此时的token是未认证的),并且将它交给ProviderManger来完成认证。
- ProviderManger中维护这一个AuthenticationProvider对象列表,通过遍历判断并且最后选择DaoAuthenticationProvider对象来完成最后的认证。
- DaoAuthenticationProvider根据ProviderManger传来的token取出username,并且调用我们写的UserDetailsService的loadUserByUsername方法从数据库中读取用户信息,然后对比用户密码,如果认证通过,则返回用户信息也是就是UserDetails对象,在重新构造UsernamePasswordAuthenticationToken(此时的token是 已经认证通过了的)。
接下来我们将通过源码来分析具体的整个认证流程。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter 是一个抽象类。所有的认证认证请求的过滤器都会继承于它,它主要将一些公共的功能实现,而具体的验证逻辑交给子类实现,有点类似于父类设置好认证流程,子类负责具体的认证逻辑,这样跟设计模式的模板方法模式有点相似。
现在我们分析一下 它里面比较重要的方法
1、doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 省略不相干代码。。。
// 1、判断当前请求是否要认证
if (!requiresAuthentication(request, response)) {
// 不需要直接走下一个过滤器
chain.doFilter(request, response);
return;
}
try {
// 2、开始请求认证,attemptAuthentication具体实现给子类,如果认证成功返回一个认证通过的Authenticaion对象
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
// 3、登录成功 将认证成功的用户信息放入session SessionAuthenticationStrategy接口,用于扩展
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
//2.1、发生异常,登录失败,进入登录失败handler回调
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
//2.1、发生异常,登录失败,进入登录失败处理器
unsuccessfulAuthentication(request, response, failed);
return;
}
// 3.1、登录成功,进入登录成功处理器。
successfulAuthentication(request, response, chain, authResult);
}
2、successfulAuthentication
登录成功处理器
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//1、登录成功 将认证成功的Authentication对象存入SecurityContextHolder中
// SecurityContextHolder本质是一个ThreadLocal
SecurityContextHolder.getContext().setAuthentication(authResult);
//2、如果开启了记住我功能,将调用rememberMeServices的loginSuccess 将生成一个token
// 将token放入cookie中这样 下次就不用登录就可以认证。具体关于记住我rememberMeServices的相关分析我 们下面几篇文章会深入分析的。
rememberMeServices.loginSuccess(request, response, authResult