持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
Spring Security登录流程的具体分析
认证过程其实就是在该系统的数据库里检查用户是否存在,存在则进行后面的授权操作。那么,用户不存在的话就没必要进行授权操作,没有意义,直接提示登录失败。
简单看一下登陆的流程,以表单登陆为例:
- 当用户提交表单的登录请求时,会由UsernamePasswordAuthenticationFilter 进行处理,它会从当前请求 HttpServletRequest 中获取用户名和密码,并且创建出UsernamePasswordAuthenticationToken 对象存储用户的信息。
- UsernamePasswordAuthenticationToken 对象将被传入 ProviderManager 中进行具体的认证操作。
- 如果认证失败,则 SecurityContextHolder 就不会存储用户的认证信息,接着会执行登录失败回调。
- 如果认证成功,则会进行登录信息存储以及登录成功方法回调等操作。 认证的流程大致是这样。接下来我们结合 AbstractAuthenticationProcessingFilter 和 UsernamePasswordAuthenticationFilter 的源码来看一下。
1. AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter的源码比较长就不贴了,其源码的作用主要如下:
(1)通过 requiresAuthentication 方法来判断当前请求是否为认证请求,如果是认证请求,就执行接下来的认证代码;如果不是认证请求,则直接执行剩余的过滤器。
(2)调用 attemptAuthentication 方法来获取一个经过认证后的 Authentication 对象, 因为attemptAuthentication 方法是一个抽象方法,其具体实现在子类UsernamePasswordAuthenticationFilter里。
(3)认证成功后,通过 sessionStrategy.onAuthentication 方法来处理 session 并发问题。
(4)continueChainBeforeSuccessfulAuthentication 变量用来判断请求是否还需要继续向下走。默认情况下该参数的值为 false,即认证成功后,后续的过滤器将不再执行了。
(5)unsuccessfulAuthentication 方法用来处理认证失败的情况,主要作用如下:
- 从 SecurityContextHolder 中清除数据;
- 清除 Cookie 等信息;
- 调用认证失败的回调方法。
(6)相反,successfulAuthentication 方法主要用来处理认证成功的,主要作用如下:
- 向 SecurityContextHolder 中存入用户信息;
- 处理 Cookie;
- 发布认证成功事件
- 调用认证成功的回调方法。
2. UsernamePasswordAuthenticationFilter
AbstractAuthenticationProcessingFilter 主要做的事情就这这些,但是还有一个抽象方法 attemptAuthentication 是在它的继承类 UsernamePasswordAuthenticationFilter 中实现的,代码如下:
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
"POST");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
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());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
(1)首先定义了默认情况下登录表单的用户名字段和密码字段,用户名字段的 key 默认 是 username,密码字段的 key 默认是 password。并且,这两个字段也可以自定义,也就是在配置类 SecurityConfig 中配置 的 .usernameParameter("uname") 和 .password Parameter("passwd")这两个参数。
(2)在 UsernamePasswordAuthenticationFilter 过滤器创建的时候,指定了这个过滤器就只是用来处理表单登录请求,登录请求默认就是/login,也可以自行配置。
(3)重头戏还是在 attemptAuthentication 方法里,在该方法中,首先要确认请求必须是 post 类型;
然后通过 obtainUsername 和 obtainPassword 方法分别从请求中获取到用户名和密码;
拿到登录请求传中的用户名和密码之后,构造出一个 authRequest,然后调用authenticate 方法进行认证。
以上所说的就是整个流程了。