文章目录
Spring Security 登录认证流程
-
- 1. UsernamePasswordAuthenticationFilter#attempt
- 2. UsernamePasswordAuthenticationToken^③^
- 3. AuthenticationManager#authenticate^④^
- 4. ProviderManager#authenticate
- 5. AbstractUserDetailsAuthenticationProvider#authenticate
- 6. DaoAuthenticationProvider#retrieveUser
- 7. UserDetailsService#loadUserByUsername
- 8.AbstractAuthenticationProcessingFilter#doFilter
- 9. Spring Security 认证流程图
Spring Security 登录认证流程
登录的整体流程是:用户点击登录从前端发送请求,后端接受前端发送来的用户名和密码,然后从数据库中查询是否存在该用户;如果存在,则放行,让用户进入系统;如果不存在或者用户名、密码错误,则提示错误信息。在 Spring Security中,大致遵循这个流程,只不过在这个过程中做了很多额外的校验工作。
1. UsernamePasswordAuthenticationFilter#attempt
用户发送登录请求,由 AbstractAuthenticationProcessingFilter#doFilter
处理,在该方法中调用其子类 UsernamePasswordAuthenticationFilter
的 attempt
方法进行处理并返回 Authentication
对象。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1.判断请求方法是否为POST
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
// 2.获取username和password(通过 request.getParameter("username") 获取)
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
// ③
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// ④
return this.getAuthenticationManager().authenticate(authRequest);
}
}
2. UsernamePasswordAuthenticationToken③
获取到请求中传递过来的用户名和密码后,构造一个 UsernamePasswordAuthenticationToken
对象,将username
和 password
传入,username
对应 principal
, password
对应 credentials
。
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
// false:用户未认证
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// true:用户已认证
super.setAuthenticated(true);
}
setDeatils()
实际是调用 WebAuthenticationDetails#buildDetails
来获取 remoteAddr
和 sessionId
并将其返回到 AbstractAuthenticationToken.details
。
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
// authenticationDetailsSource = new WebAuthenticationDetailsSource();
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
// 实际调用的是 WebAuthenticationDetails#buildDetails
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new WebAuthenticationDetails(context);
}
public WebAuthenticationDetails(HttpServletRequest request) {
this.remoteAddress = request.getRemoteAddr();
HttpSession session = request.getSession(false);
this.sessionId = session != null ? session.getId() : null;
}
UsernamePasswordAuthenticationToken 总结
UsernamePasswordAuthenticationToken
继承 AbstractAuthenticationToken
,是一个放置认证对象信息的类,拥有的属性分别是:
principal
:对应用户名;credentials
:对应密码;authenticated
:是否已认证;authorities
:权限集合;details
:其他细节,setDetails()
后变成一个WebAuthenticationDetails
对象,里面有两个属性,remoteAddr 和 sessionId。
3. AuthenticationManager#authenticate④
在构建 UsernamePasswordAuthenticationToken
对象后,执行 AuthenticationManager
的 authenticae
方法。AuthenticationManager
接口中只有一个 authenticae
方法。该接口的实现类 ProviderManager
中有 authenticate
方法。
ProviderManager
内部维护一个List表,存放多种认证逻辑(用户名+密码,邮箱+密码等等),不同的认证逻辑对应不同的 AuthenticationProvider
。
4. ProviderManager#authenticate
public Authentication authenticate(Authentication authentication) {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
// 1.获取传入的 Authentication,判断当前 provider 是否 support 该 Authentication。
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
// 2.如果支持,则调用 provider的authenticate方法 进行校验
// 校验完成后会返回一个新的Authentication。
result = provider.authenticate(authentication);
...
}
}
// 3.provider有多个,如果 provider的authenticate方法 未能正常返回一个 Authentication,则调用 provider的parent的authenticate方法 继续校验。
if (result == null && parent != null) {
try {
result = parentResult = parent.authenticate(authentication);
}
...
}
...
}
遍历所有 AuthenticationProvider
并通过 supports
方法判断其是否支持传入的 Authentication
。找到支持 UsernamePasswordAuthenticationToken
的 provider
,即DaoAuthenticationProvider
,进入该类的 authenticate
方法,但该类中并未重写 authenticate
方法,于是来到其父类 AbstractUserDetailsAuthenticationProvider
的 authenticate
方法。
5. AbstractUserDetailsAuthenticationProvider#authenticate
- 从
Authentication
中获取登录的用户名。通过用户名调用retrieveUser()
方法获取UserDetails
。 - 在获取到
UserDetails
之后执行以下验证方法:preAuthenticationChecks.check
方法:验证用户中的各状态属性是否正常,例如:是否被禁用、是否被锁定等。additionalAuthenticationChecks.check
方法:密码比对。post AuthenticationChecks.check
方法:检查密码是否过期。createSuccessAuthentication
方法:构建一个新的UsernamePasswordAuthenticationToken
返回到UsernamePasswordAuthenticationFilter
中 。
6. DaoAuthenticationProvider#retrieveUser
调用 UserDetailsService#loaUserByUsername 去数据库中读取用户信息,将其封装并返回 UserDetails。
7. UserDetailsService#loadUserByUsername
编写 XXXUserDetails
实现类实现 UserDetails
接口,在这个实现类中重写 loadUserByUsername
方法从数据库中查询数据;最后返回一个 UserDetails
对象,返回到 AbstractUserDetailsAuthenticationProvider
执行一系列验证方法。
8.AbstractAuthenticationProcessingFilter#doFilter
通过上述描述可知:用户发送登录请求,由 AbstractAuthenticationProcessingFilter#doFilter
处理,在该方法中调用其子类 UsernamePasswordAuthenticationFilter
的 attempt
方法进行处理并返回 Authentication
对象(AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication
)。
最后,根据认证的成功或者失败调用相应的 handler
successfulAuthentication
:该方法就是将认证信息存储到 Session 中。
successfulAuthentication
:该方法就是将认证信息存储到 Session 中。
SecurityContextHolder.getContext().setAuthentication(authResult);
unsuccessfulAuthentication
SecurityContextHolder.clearContext();
9. Spring Security 认证流程图