前言
对 Spring Security 的文章也写了好几篇了,有基本使用的,有对其进行自定义逻辑的。但那时各位肯定不知道为什么需要这么去定义。所以,本篇呢,来理一理 Spring Security 中默认的表单认证流程源码!
依赖版本
名称 | 版本 |
---|---|
spring-boot-starter-parent | 2.3.12.RELEASE |
spring-boot-starter-security | 2.3.12.RELEASE |
spring-security-web | 5.3.9.RELEASE |
调试开始
就加入 Spring Security 依赖后,启动项目,访问登录页面,在页面输入默认的用户名、密码后,点击提交按钮,会发起一个请求类型为 POST,请求路径为 /login
的接口。 该请求会被 Spring Security 的过滤器链代理拦截。
该请求会来到 UsernamePasswordAuthenticationFilter
。
那 UsernamePasswordAuthenticationFilter
这个对象是怎么来的呢? 我们可以看看 WebSecurityConfigurerAdapter
抽象类中的 configure(HttpSecurity http)
方法中提供了一个默认的配置,代码如下:
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
复制代码
- authorizeRequests:允许使用 RequestMatcher 实现(即通过URL模式)基于 HttpServletRequest 限制请求访问。
- anyRequest().authenticated():请求都需要被认证
- formLogin:指定支持基于表单的身份验证。如果
FormLoginConfigurer
未指定 loginPage,将生成默认登录页。 - httpBasic:可以配置basic登录
进入 formLogin 方法。
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
复制代码
可以看到这里创建了一个 FormLoginConfigurer
对象,FormLoginConfigure
是一个用户名密码表单登录的配置类,比如设置登录页面的Url,登录请求的Url 、登录认证成功、认证失败的回调、设置登录请求参数名等等。
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
复制代码
在其无参构造方法中,创建了一个 UsernamePasswordAuthenticationFilter
对象。将其传递给父类,父类会将该过滤器添加到过滤器链中。
AbstractAuthenticationProcessingFilter
上面说到请求会来到 UsernamePasswordAuthenticationFilter
,但是它继承了 AbstractAuthenticationProcessingFilter
,所以请求会先进入 AbstractAuthenticationProcessingFilter
抽象类中,它是一个 Filter
,那我们将断点打在 doFilter 方法中。
介绍一下 chain 中的属性,originalChain 表示原生的过滤器链,也就是 Web Filter;additionalFilters 表示 Spring Security 中的过滤器链;firewalledRequest 表示当前请求;size 表示过滤器链中过滤器的个数;currentPosition 则是过滤器链遍历时候的下标。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 进行请求过滤,判断当前调用的请求是否有适配的 xxxAuthenticationFilter 处理
// 因为 AbstractAuthenticationProcessingFilter 是一个过滤器,那就代表所有的接口请求都会进入该类的 doFilter 方法,
// 但又不是所有接口都是需要进行登录认证流程的,所以提前进行请求匹配过滤
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
// 身份验证对象
Authentication authResult;
try {
// 调用子类具体实现的 attemptAuthentication(尝试身份验证) 方法
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
// 认证成功时,session 会话需要执行的逻辑
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
// 身份验证失败的默认处理
// 1、清除 SecurityContextHolder
// 2、通知配置的RememberMeServices登录失败
// 3、将其他行为委托给 AuthenticationFailureHandler,调用其登录失败回调方法(实际开发中会重写AuthenticationFailureHandler接口)
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// 认证成功
// 在successfulAuthentication执行前继续执行过滤器(默认为 false)
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 身份验证成功的处理
// 1、在Sec