请求进来时,检查session,如果有SecurityContext(已认证用户对象的一个封装类)拿出来放到线程里即SecurityContextHolder中,返回时校验SecurityContextHolder中是否有securityContext,有则放入session,从而实现认证信息在多个请求中共享。
我觉得AbstractAuthenticationProcessingFilter是理解表单表单登陆认证原理最重要的一个类
其部分源码和注释如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {//如果不需要认证直接放行
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}
Authentication authResult;
try {//走到这里说明用户没有进行登陆认证,而下面的方法就是去尝试进行登陆认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {return;}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {//认证失败走unsuccessfulAuthentication
logger.error("An internal error occurred while trying to authenticate the user.",failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {//认证失败走unsuccessfulAuthentication
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success-->应该是如果走到这,发现不需要再走successfulAuthentication方法了也直接放行
if (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}
//认证成功后需要做的事
successfulAuthentication(request, response, chain, authResult);
}
我们再看一下如果认证成功后具体干了什么事 ,successfulAuthentication源码如下:
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//将认证后的结果放入到SecurityContextHolder中的SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
//与记住我功能相关认证方式------之后的博客中应该会有涉及
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event----还没具体研究是干什么的
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//执行登陆成功后的逻辑-----即实现了AuthenticationSuccessHandler接口的类中定义的逻辑
successHandler.onAuthenticationSuccess(request, response, authResult);
}
再看一下AbstractAuthenticationProcessingFilter类中包含的方法(对一些重要方法做了标记)
通过上面的解读可以知道AbstractAuthenticationProcessingFilter这个类定义了认证的整个整体框架,但是具体的认证过程、认证成功后的行为和认证失败后的行为需要其他类进行具体实现,该类的伪代码如下:
按照上述分析,要想进行登陆认证,必然会有一个类继承AbstractAuthenticationProcessingFilter类,并实现其attemptAuthentication抽象方法。而UsernamePasswordAuthenticationFilter正是用户名、密码登录认证过程中的这个类,因此实际上UsernamePasswordAuthenticationFilter并不是一个Filter,它只是继承了AbstractAuthenticationProcessingFilter并实现了其attemptAuthentication抽象方法。
UsernamePasswordAuthenticationFilter关键源代码:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//利用用户名和密码组装成一个未校验的token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//拿着用户名和密码组成的token进行校验,注意返回值为Authentication
//同时注意AuthenticationManager来自父类AbstractAuthenticationProcessingFilter
return this.getAuthenticationManager().authenticate(authRequest);
}
UsernamePasswordAuthenticationFilter很简单,主要做了三件事:
生成未认证的token的源代码,这里需要注意的是返回值UsernamePasswordAuthenticationToken实际上是一个Authentication,它继承了AbstractAuthenticationToken,而AbstractAuthenticationToken实现了Authentication接口
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
其实AuthenticationManager为一个接口,它里面只有一个抽象方法authenticate;
ProviderManager为AuthenticationManager的一个实现类,它实现了AuthenticationManager的authenticate方法,但是它并不是直接去进行登录认证,而是去循环所有的AuthenticationProvider;
那AuthenticationProvider又是干什么的呢?其实它也是一个接口,里面包含两个抽象方法
public interface AuthenticationProvider {
//登陆认证
Authentication authenticate(Authentication authentication) throws AuthenticationException;
//判断传入的token是否为支持的认证类型
boolean supports(Class<?> authentication);
}
AuthenticationProvider的实现类有这么多:
AbstractUserDetailsAuthenticationProvider类为用户名和密码方式登陆最后选择出的进行登陆校验的实际执行类,主要的校验逻辑如下(我对源代码进行了删减):
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
//获取用户信息
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
}catch (UsernameNotFoundException notFound) {}
try {
//预检查---四个权限中的三个是否为false
preAuthenticationChecks.check(user);
//用户名密码是否正确
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}}catch (UsernameNotFoundException notFound) {}
//后检查---四个权限中的最后一个是否为false
postAuthenticationChecks.check(user);
//都校验通过后创建一个校验成功后的Authentication对象
return createSuccessAuthentication(principalToReturn, authentication, user);
}
检验成功的Authentication创建源码:
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
其实AbstractUserDetailsAuthenticationProvider的设计很像AbstractAuthenticationProcessingFilter,它里面也定义了一个主要的框架,但retrieveUser、additionalAuthenticationChecks方法的具体实现都在DaoAuthenticationProvider中,当然AbstractUserDetailsAuthenticationProvider里面还有一些方法的实现在其他类中。
首先来看一下DaoAuthenticationProvider中关于retrieveUser方法的具体实现:
try {
//调用UserDetailsService的loadUserByUsername方法获取用户详情信息
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
由此我们可以定位到UserDetailsService其实是一个接口,它里面只有一个方法loadUserByUsername,当我们实现了该接口时spring-security就会调用我们实现的接口,进行用户的信息校验,这在前面的文章-spring-security入门2—自定义用户认证逻辑部分进行过讲解。
上面的内容已经对表单登陆认证的源码进行了比较全面的剖析,为了能更好地与前面几篇文章串起来,我将文章开篇展示的图片进行了如下标注,以求能更好的串联最近所学知识点。