文章目录
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()
对象,里面有两个属性,remoteAddr 和 sessionId。WebAuthenticationDetails
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();