Spring Security源码分析(一)
SpringSecurity基本原理是基于一个过滤器链
我们启动一个SpringSecurity项目,日志会打印过滤器
一共有十二个过滤器,大致介绍一下
- WebAsyncManagerIntegrationFilter将Security上下文与Spring Web中用于处理异步请求映射的 WebAsyncManager 进行集成
- SecurityContextPersistenceFilter 两个主要职责:请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder。
- HeaderWriterFilter (文档中并未介绍,非核心过滤器) 用来给http响应添加一些Header,比如X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options.
- CsrfFilter 在spring4这个版本中被默认开启的一个过滤器,用于防止csrf攻击,了解前后端分离的人一定不会对这个攻击方式感到陌生,前后端使用json交互需要注意的一个问题。
- LogoutFilter 顾名思义,处理注销的过滤器
UsernamePasswordAuthenticationFilter 这个会重点分析,表单提交了username和password,被封装成token进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。 - RequestCacheAwareFilter (文档中并未介绍,非核心过滤器) 内部维护了一个RequestCache,用于缓存request请求
- SecurityContextHolderAwareRequestFilter 此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
- AnonymousAuthenticationFilter 匿名身份过滤器
- UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
- SessionManagementFilter 和session相关的过滤器,内部维护了一个SessionAuthenticationStrategy,两者组合使用,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
- ExceptionTranslationFilter 直译成异常翻译过滤器,还是比较形象的,这个过滤器本身不处理异常,而是将认证过程中出现的异常交给内部维护的一些类去处理,具体是那些类下面详细介绍
- FilterSecurityInterceptor 这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。
最常用的就是UsernamePasswordAuthenticationFilter 了
接下来我们就对UsernamePasswordAuthenticationFilter进行一个源码分析
UsernamePasswordAuthenticationFilter
核心方法:attemptAuthentication做了以下几件事情
- 判断是不是post请求,不是则抛异常
- 获取用户名和密码,判空
- 构建UsernamePasswordAuthenticationToken(将权限和用户名和密码进行一个封装)
- 执行setDetails,将请求的信息存入token里
- 通过AuthenticationManager的authenticate()去进行校验
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
然而AuthenticationManager是一个接口,对应的实现类是ProviderManager,查看以下authenticate的实现方法
ProviderManager 中的List,会依照次序去认证,认证成功则立即返回,若认证失败则返回null,下一个AuthenticationProvider会继续尝试认证,如果所有认证器都无法认证成功,则ProviderManager
会抛出一个ProviderNotFoundException异常。
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
......
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
}
}
所以实现校验的过程的是AuthenticationProvider的实现类,比如使用表单验证的情况下,默认是使用DaoAuthenticationProvider
DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider,authenticate方法aut是在AbstractUserDetailsAuthenticationProvider
authenticate方法做了什么呢?
//1.获取UserDetails
UserDetails user = this.userCache.getUserFromCache(username);
//2.通过UserDetailsService去获取UserDeatails对象
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
//3.预检查,对UserDetails进行提前的检查,判断是否锁定,是否过期,是否可用
preAuthenticationChecks.check(user);
//4.附加检查,判断是否有证书和是否有用passwordEncoder加密
additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
//5.最后检查,判断证书是否过期
postAuthenticationChecks.check(user);
//6.返回认证成功的信息
return createSuccessAuthentication(principalToReturn, authentication, user);
reateSuccessAuthentication(principalToReturn, authentication, user)源码:
重写组装了一个UsernamePasswordAuthenticationToken,和第一次的组装不一样的地方在于
添加了权限和设置了已验证的信息
//第一次的组装
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
//第二次的组装
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
整个过程 一旦任何一处发送异常,都会被捕获AbstractAuthenticationProcessingFilter类里面捕获
- 异常则调用unsuccessfulAuthentication()
- failureHandler.onAuthenticationFailure(request, response, failed);会调用自己写的failureHandler
- 成功则调用successfulAuthentication(),
- successHandler.onAuthenticationSuccess(request, response, authResult);会调用自己写的successHandler
try {
//这里是UsernamePasswordAuthenticationFilter的最后返回值
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//调用登陆成功的处理器
successfulAuthentication(request, response, chain, authResult);