认证流程图
相关类
UsernamePasswordAuthenticationFilter是一个过滤器,它继承了抽象类AbstractAuthenticationProcessingFilter,负责表单登录请求,
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 String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
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());
} else {
// 获取用户名
String username = this.obtainUsername(request);
// 获取用户登录密码
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 用户username和password构造一个UsernamePasswordAuthenticationToken对象
// UsernamePasswordAuthenticationToken也是Authentication几口的一个实现
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 将用户的一些信息设置到authRequest
this.setDetails(request, authRequest);
// 调用AuthenticationManager的authenticate方法进行验证,如果验证过程中失败就会抛出异常,如果认证成功则会返回一个经过认证的Authentication对象
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.usernameParameter;
}
public final String getPasswordParameter() {
return this.passwordParameter;
}
}
Authentication接口
public interface Authentication extends Principal, Serializable {
// 1. 权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
Collection<? extends GrantedAuthority> getAuthorities();
// 2. 用户名密码认证时可以理解为密码
Object getCredentials();
// 3. 认证时包含的一些信息。
Object getDetails();
// 4. 用户名密码认证时可理解时用户名
Object getPrincipal();
// 5. 是否被认证,认证为true
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
AuthenticationManager
// 认证方法的入口,没有验证逻辑,是用来管理AuthenticationProvider
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
ProviderManager实现了AuthenticationManager,用来获取AuthenticationProvider,AuthenticationProvider实现验证逻辑。
它提供了基本的认证逻辑和方法;它包含了一个 List<AuthenticationProvider>
对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者(当Spring Security
默认提供的实现类不能满足需求的时候可以扩展AuthenticationProvider
覆盖supports(Class<?> authentication)
方法);
ProviderManager部分代码如下
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
// 由于可能存在多种认证方法,例如用户名密码认证、短信认证,所以选择当前是属于哪种认证方法
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
// 认证过程
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var11) {
this.prepareException(var11, authentication);
throw var11;
} catch (InternalAuthenticationServiceException var12) {
this.prepareException(var12, authentication);
throw var12;
} catch (AuthenticationException var13) {
lastException = var13;
}
}
}
}
AuthenticationProvider也是一个接口,AbstractUserDetailsAuthenticationProvider和DaoAuthenticationProvider都是它的子类
public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
AbstractUserDetailsAuthenticationProvider是一个抽象类,AbstractUserDetailsAuthenticationProvider主要实现了AuthenticationProvider的接口方法authenticate并提供了相关的验证逻辑,部分代码如下。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
// retrieveUserretrieveUser是AbstractUserDetailsAuthenticationProvider的一个抽象方法,它是由
// 子类DaoAuthenticationProvider实现的
// 通过用户名获取用户信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
// 检查账户是否locked、disabled、expired
this.preAuthenticationChecks.check(user);
// 校验密码是否正确
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
// 校验账户credentials是否过期
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 到这里说明已经登陆成功了,返回一个已经经过认证的Authentication对象
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider抽象类,
DaoAuthenticationProvider部分代码如下
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
// getUserDetailsService()返回一个UserDetailsService对象,这里就和前面介绍
// 如何实现UserDetailsService接口返回一个用户对象连接上了,就相当于程序运行到这里,就会调用
// 之前我们自己实现的MyUserService类中的loadUserByUsername方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
身份认证成功后,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用successfulAuthentication方法中,这个方法最后会返回我们自己定义的登录成功处理器handler,在返回之前,它会调用SecurityContext,最后将认证的结果放入SecurityContextHolder中,SecurityContext类很简单,重写了equals方法和hascode方法,保证了authentication的唯一性。SecurityContextHolder类实际上对ThreadLocal的一个封装。因此可以在同一个线程中的不同方法中获取到认证信息。最后会被SecurityContextPersistenceFilter过滤器使用,这个过滤器的作用是:当一个请求来的时候,它会将session中的值传入到该线程中,当请求返回的时候,它会判断该请求线程是否有SecurityContext,如果有它会将其放入到session中,因此保证了请求结果可以在不同的请求之间共享。
如果身份认证失败,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用unsuccessfulAuthentication方法中,这个方法最后会返回我们自己定义的登录失败处理器handler
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 没有抛出异常则说明认证成功
this.successfulAuthentication(request, response, chain, authResult);
}
}
// 该方法由子类UsernamePasswordAuthenticationFilter实现
public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;
// 认证成功
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
// 将认证信息存在SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
// 认证成功后,将会调用我们前面实现过的MyAuthenticationSuccessHandler类
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
// 认证失败
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
this.rememberMeServices.loginFail(request, response);
// 认证失败后,将会调用我们前面实现过的MyAuthenticationSuccessHandler类
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
SecurityContextPersistenceFilter
请求进来时,检查Session中是否有SecurityContext,如果有就从Session中拿出并放到线程中,如果没有就往下执行
当认证过程走完,最后回到这里时,检查线程,如果线程中存在SecurityContext,就将其取出并存到Session中
获取认证信息
@GetMapping("/me1")
public Object me1() {
return SecurityContextHolder.getContext().getAuthentication();
}
@GetMapping("/me2")
public Object me2(Authentication authentication) {
return authentication;
}
@GetMapping("/me3")
public Object me3(Principal principal) {
return principal;
}
参考文章 http://www.spring4all.com/article/439