Spring Security源码分析(一)

Spring Security源码分析(一)

SpringSecurity基本原理是基于一个过滤器链
我们启动一个SpringSecurity项目,日志会打印过滤器
在这里插入图片描述
在这里插入图片描述
一共有十二个过滤器,大致介绍一下

  1. WebAsyncManagerIntegrationFilter将Security上下文与Spring Web中用于处理异步请求映射的 WebAsyncManager 进行集成
  2. SecurityContextPersistenceFilter 两个主要职责:请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder。
  3. HeaderWriterFilter (文档中并未介绍,非核心过滤器) 用来给http响应添加一些Header,比如X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options.
  4. CsrfFilter 在spring4这个版本中被默认开启的一个过滤器,用于防止csrf攻击,了解前后端分离的人一定不会对这个攻击方式感到陌生,前后端使用json交互需要注意的一个问题。
  5. LogoutFilter 顾名思义,处理注销的过滤器
    UsernamePasswordAuthenticationFilter 这个会重点分析,表单提交了username和password,被封装成token进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。
  6. RequestCacheAwareFilter (文档中并未介绍,非核心过滤器) 内部维护了一个RequestCache,用于缓存request请求
  7. SecurityContextHolderAwareRequestFilter 此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
  8. AnonymousAuthenticationFilter 匿名身份过滤器
  9. UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
  10. SessionManagementFilter 和session相关的过滤器,内部维护了一个SessionAuthenticationStrategy,两者组合使用,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
  11. ExceptionTranslationFilter 直译成异常翻译过滤器,还是比较形象的,这个过滤器本身不处理异常,而是将认证过程中出现的异常交给内部维护的一些类去处理,具体是那些类下面详细介绍
  12. FilterSecurityInterceptor 这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。

最常用的就是UsernamePasswordAuthenticationFilter
接下来我们就对UsernamePasswordAuthenticationFilter进行一个源码分析

UsernamePasswordAuthenticationFilter

核心方法:attemptAuthentication做了以下几件事情

  1. 判断是不是post请求,不是则抛异常
  2. 获取用户名和密码,判空
  3. 构建UsernamePasswordAuthenticationToken(将权限和用户名和密码进行一个封装)
  4. 执行setDetails,将请求的信息存入token里
  5. 通过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类里面捕获

  1. 异常则调用unsuccessfulAuthentication()
    1. failureHandler.onAuthenticationFailure(request, response, failed);会调用自己写的failureHandler
  2. 成功则调用successfulAuthentication(),
    1. 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);

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值