文章目录
- 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 中。SecurityContextHolder.getContext().setAuthentication(authResult); -
unsuccessfulAuthenticationSecurityContextHolder.clearContext();
-
9. Spring Security 认证流程图











