总体的认证流程如下:
登录的请求首先会被拦截器UsernamePasswordAuthenticationFilter
拦截,第一次请求肯定是未认证,未认证通过对象AuthenticationManager
,来委托AuthenticationProvider
,去关联UserDetailService
,去查数据库,判断用户是否是数据库中存在的用户,当认证通过后,把数据封装到UserDetail
中,再然后把认证 的信息,封装到Authentication
中
UsernamePasswordAuthenticationFilter
认证过滤器,继承自AbstractAuthenticationProcessingFilter
。
它的doFilter
方法,其实是在父类AbstractAuthenticationProcessingFilter
中,它只是重写了一些方法而已。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
1、判断当前请求是不是post请求
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 {
//2、调用子类的方法进行身份认证,认证成功之后,把认证信息封装到对象里面去
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
//3、认证成功之后,用session存储相关信息。session策略处理
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
//4、认证失败,做认证失败的处理
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
//4、认证失败,做认证失败的处理
this.unsuccessfulAuthentication(request, response, var9);
return;
}
//5、认证成功后的处理
if (this.continueChainBeforeSuccessfulAuthentication) {
//执行下一个过滤器
chain.doFilter(request, response);
}
//调用认证成功后的方法
this.successfulAuthentication(request, response, chain, authResult);
}
}
这个requiresAuthenticationRequestMatcher其实最终是来源于子类UsernamePasswordAuthenticationFilter的构造方法
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return this.requiresAuthenticationRequestMatcher.matches(request);
}
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
UsernamePasswordAuthenticationFilter的构造方法
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
如果我们自定义一个认证过滤器,只需要继承自UsernamePasswordAuthenticationFilter
,然后在构造方法中,调用setRequiresAuthenticationRequestMatcher
就能覆盖UsernamePasswordAuthenticationFilter
设置的默认的登录路径。
UsernamePasswordAuthenticationFilter
:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//判断是否是post请求
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//从请求的queryString中获取用户名和密码
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//将用户名和密码封装到UsernamePasswordAuthenticationToken中
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//再将request请求,封装到UsernamePasswordAuthenticationToken对象中
this.setDetails(request, authRequest);
//将未认证的信息调用authenticate方法进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
//从请求的queryString中获取用户名和密码
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
ProviderManager
是 AuthenticationManager
接口的实现类,该接口是认证相关的核心接口,也是认证的入口。在实际开发中,我们可能有多种不同的认证方式,例如:用户名+密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是AuthenticationManager
。在该接口的常用实现类 ProviderManager
内部会维护一个List
列表,存放多种认证方式,实际上这是委托者模式(Delegate)
的应用。每种认证方式对应着一个 AuthenticationProvider
,AuthenticationManager
根据认证方式的不同(根据传入的 Authentication
类型判断)委托对应的 AuthenticationProvider
进行用户认证。
ProviderManager
的authenticate
方法的认证过程。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取传入的Authentication 类型的UsernamePasswordAuthenticationToken.class
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
//获取认证方式列表List<AuthenticationProvider>的迭代器
Iterator var8 = this.getProviders().iterator();
//循环迭代
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
//判断当前的AuthenticationProvider是否支持UsernamePasswordAuthenticationToken.class类型的Authentication
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
//重点!!!!!!!!!!!!!!!!!!!!!!!!!!!
//成功找到适配当前认证方式的AuthenticationProvider,此处为DaoAuthenticationProvider
//如果认证成功,会返回一个标记已认证的Authentication对象
result = provider.authenticate(authentication);
if (result != null) {
//认证成功,将传入的Authentication对象中的details信息拷贝到已认证的Authentication对象中
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
}
if (result == null && this.parent != null) {
try {
//认证失败,使用父类型的AuthenticationManager进行验证
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}
if (result != null) {
//认证成功之后,去除result的敏感信息,要求相关类实现CredentialsContainer接口
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
//去除过程就是调用CredentialsContainer接口的eraseCredentials方法
((CredentialsContainer)result).eraseCredentials();
}
//发布认证成功的事件
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
//认证失败之后,抛出失败的异常信息
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{
toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
看重点:DaoAuthenticationProvider
的authenticate
方法,实际上是父类AbstractUserDetailsAuthenticationProvider
的authenticate
方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
//尝试从缓存中获取user
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//重点!!!!!
//缓存中没有获取到用户,直接去数据库中检索用户
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 {
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);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
//把获取到的用户放到缓存中
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
看重点:发现retrieveUser
方法被子类DaoAuthenticationProvider
,重写了
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//获取一个UserDetailsService,并执行它的loadUserByUsername方法,来获取一个用户
//查询数据库获取用户的逻辑,就写在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);
}
}
认证过程最重要的部分就解析完了
回到AbstractAuthenticationProcessingFilter
,查看successfulAuthentication
和unsuccessfulAuthentication
方法
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);
}
//将认证成功的用户信息对象Authentication封装进SecurityContext对象中
//SecurityContextHolder是对ThreadLocal的一个封装
SecurityContextHolder.getContext().setAuthentication(authResult);
//remenberMe的处理
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
//发布认证成功的事件
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//调用认证成功处理器
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
//清除该线程在SecurityContextHolder中对应的SecurityContext对象
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);
}
//rememberMe的处理
this.rememberMeServices.loginFail(request, response);
//调用认证失败处理器
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
上一个部分通过源码的方式介绍了认证流程,下面介绍权限访问流程,主要是对ExceptionTranslationFilter
过滤器和 FilterSecurityInterceptor
过滤器进行介绍。
该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。具体源码如下:
FilterSecurityInterceptor
是过滤器链的最后一个过滤器,该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器。
ExceptionTranslationFilter
进行捕获和处理。具体源码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null && this.observeOncePerRequest) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
//根据资源权限配置来判断当前请求是否有权限访问对应的资源
//如果不能访问则抛出异常
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//访问相关资源,通过Springmvc的核心组件DispatchServlet进行访问
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
//待请求完成后会在 finallyInvocation() 中将原来的 SecurityContext 重新设置给SecurityContextHolder。
super.finallyInvocation(token);
}
// 正常请求结束,最后也会执行(afterInvocation 内部会调用finallyInvocation )
super.afterInvocation(token, (Object)null);
}
}
需要注意,Spring Security
的过滤器链是配置在 SpringMVC
的核心组件DispatcherServlet
运行之前。也就是说,请求通过 Spring Security
的所有过滤器,不意味着能够正常访问资源,该请求还需要通过 SpringMVC
的拦截器链。
看看beforeInvocation
:
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
} else {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
if (debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
//判断是否需要进行身份认证
Authentication authenticated = this.authenticateIfRequired();
try {
//使用获取到的ConfigAttribute ,继续调用访问控制器AccessDecisionManager对当前请求进行鉴权。
//无论鉴权通过或是不通后,Spring Security 框架均使用了观察者模式,来通知其它Bean,当前请求的鉴权结果。
//如果鉴权不通过,则会抛出 AccessDeniedException 异常,即访问受限,然后会被 ExceptionTranslationFilter 捕获,最终解析后调转到对应的鉴权失败页面
//如果鉴权通过,AbstractSecurityInterceptor 通常会继续请求
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var7) {
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
throw var7;
}
if (debug) {
this.logger.debug("Authorization successful");
}
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
//通过 RunAsManager 在现有 Authentication 基础上构建一个新的Authentication
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
if (debug) {
this.logger.debug("RunAsManager did not change Authentication object");
}
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
} else {
if (debug) {
this.logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
//如果新的 Authentication 不为空则将产生一个新的 SecurityContext,并把新产生的Authentication 存放在其中
//这样在请求受保护资源时从 SecurityContext中 获取到的 Authentication 就是新产生的 Authentication。
SecurityContextHolder.getContext().setAuthentication(runAs);
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
// rejectPublicInvocations 属性,默认为 false。此属性含义为拒绝公共请求
} else if (this.rejectPublicInvocations) {
throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'");
} else {
if (debug) {
this.logger.debug("Public object - authentication not attempted");
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
authenticateIfRequired
:判断是否需要进行身份认证
private Authentication authenticateIfRequired() {
//从SecurityContextHolder获取authentication
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//判断是否已经认证过了。
//还记得UsernamePasswordAuthenticationToken的构造方法有设置是否认证过吗
if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Previously Authenticated: " + authentication);
}
//认证过了,直接返回authentication
return authentication;
} else {
//没有认证过,调用相应的ProviderManager去认证
authentication = this.authenticationManager.authenticate(authentication);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Successfully Authenticated: " + authentication);
}
//认证完,把authentication 设置进SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
一般认证成功后的用户信息是通过 Session
在多个请求之间共享,那么 Spring Security
中是如何实现将已认证的用户信息对象 Authentication
与 Session
绑定的进行具体分析。
在前面讲解认证成功的处理方法 successfulAuthentication
() 时,有以下代码:
查 看 SecurityContext
接 口 及 其 实 现 类 SecurityContextImpl
, 该 类 其 实 就 是 对Authentication
的封装:
查看 SecurityContextHolder
类 , 该类其实是对ThreadLocal
的封装,存储SecurityContext
对象:
前面提到过,在 UsernamePasswordAuthenticationFilter
过滤器认证成功之后,会在认证成功的处理方法中将已认证的用户信息对Authentication
封装进SecurityContext
,并存入 SecurityContextHolder
。
之后,响应会通过 SecurityContextPersistenceFilter
过滤器,该过滤器的位置在所有过滤器的最前面,请求到来先进它,响应返回最后一个通过它,所以在该过滤器中处理已认证的用户信息对象 Authentication
与 Session
绑定。
认证成功的响应通过 SecurityContextPersistenceFilter
过滤器时,会从SecurityContextHolder
中取出封装了已认证用户信息对象 Authentication
的SecurityContext
,放进 Session
中。当请求再次到来时,请求首先经过该过滤器,该过滤器会判断当前请求的 Session
是否存SecurityContext
对象,如果有则将该对象取出再次放入 SecurityContextHolder
中,之后该请求所在的线程获得认证用户信息,后续的资源访问不需要进行身份认证;当响应再次返回时,该过滤器同样从 SecurityContextHolder
取出SecurityContext
对象,放入 Session
中。具体源码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (request.getAttribute("__spring_security_scpf_applied") != null) {
chain.doFilter(request, response);
} else {
boolean debug = this.logger.isDebugEnabled();
request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
this.logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//请求到来,检查当前session中是否存有SecurityContext对象,如果有,从session中取出该对象
//如果没有,创建一个空的SecurityContext对象
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
var13 = true;
//将上述获得的SecurityContext对象放入到SecurityContextHolder中
SecurityContextHolder.setContext(contextBeforeChainExecution);
//进入到下一个过滤器
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if (var13) {
//响应返回时,从SecurityContextHolder中取出SecurityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//移除SecurityContextHolder中的SecurityContext
SecurityContextHolder.clearContext();
//将取出的SecurityContext对象放进session中
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContextPersistenceFilter
是在SecurityContextConfigurer
中被addFilter