SpringSecurity基本原理
在之前的文章《SpringBoot + Spring Security 基本使用及个性化登录配置》中对SpringSecurity进行了简单的使用介绍,基本上都是对于接口的介绍以及功能的实现。 这一篇文章尝试从源码的角度来上对用户认证流程做一个简单的分析。
在具体分析之前,我们可以先看看SpringSecurity的大概原理:
其实比较简单,主要是通过一系列的Filter对请求进行拦截处理。
认证处理流程说明
我们直接来看UsernamePasswordAuthenticationFilter
类,
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
// 登录请求认证
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 {
// 获取用户,密码
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 生成Token,
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 进一步验证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
在attemptAuthentication
方法中,主要是进行username和password请求值的获取,然后再生成一个UsernamePasswordAuthenticationToken 对象,进行进一步的验证。
不过我们可以先看看UsernamePasswordAuthenticationToken 的构造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
// 设置空的权限
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
// 设置是否通过了校验
this.setAuthenticated(false);
}
其实UsernamePasswordAuthenticationToken是继承于Authentication
,该对象在上一篇文章中有提到过,它是处理登录成功回调方法中的一个参数,里面包含了用户信息、请求信息等参数。
所以接下来我们看
this.getAuthenticationManager().authenticate(authRequest);
这里有一个AuthenticationManager,但是真正调用的是ProviderManager
。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
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();
// 1.判断是否有provider支持该Authentication
if (provider.supports(toTest)) {
// 2. 真正的逻辑判断
result = provider.authenticate(authentication);
}
}
}
- 这里首先通过provider判断是否支持当前传入进来的Authentication,目前我们使用的是UsernamePasswordAuthenticationToken,因为除了帐号密码登录的方式,还会有其他的方式,比如SocialAuthenticationToken。
- 根据我们目前所使用的UsernamePasswordAuthenticationToken,provider对应的是
DaoAuthenticationProvider
。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
// 1.去获取UserDetails
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
}
try {
// 2.用户信息预检查
this.preAuthenticationChecks.check(user);
// 3.附加的检查(密码检查)
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
}
// 4.最后的检查
this.postAuthenticationChecks.check(user);
// 5.返回真正的经过认证的Authentication
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
- 去调用自己实现的UserDetailsService,返回UserDetails
- 对UserDetails的信息进行校验,主要是帐号是否被冻结,是否过期等
- 对密码进行检查,这里调用了PasswordEncoder
- 检查UserDetails是否可用。
- 返回经过认证的Authentication
这里的两次对UserDetails的检查,主要就是通过它的四个返回boolean类型的方法。
经过信息的校验之后,通过UsernamePasswordAuthenticationToken
的构造方法,返回了一个经过认证的Authentication。
拿到经过认证的Authentication之后,会再去调用successHandler。或者未通过认证,去调用failureHandler。
认证结果如何在多个请求之间共享
再完成了用户认证处理流程之后,我们思考一下是如何在多个请求之间共享这个认证结果的呢?
因为没有做关于这方面的配置,所以可以联想到默认的方式应该是在session中存入了认证结果。
那么是什么时候存放入session中的呢?
我们可以接着认证流程的源码往后看,在通过attemptAuthentication
方法后,如果认证成功,会调用successfulAuthentication
,该方法中,不仅调用了successHandler,还有一行比较重要的代码
SecurityContextHolder.getContext().setAuthentication(authResult);
SecurityContextHolder是对于ThreadLocal的封装。 ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。 更多的关于ThreadLocal的原理可以看看我以前的文章。
一般来说同一个接口的请求和返回,都会是在一个线程中完成的。我们在SecurityContextHolder中放入了authResult,再其他地方也可以取出来的。
最后就是在SecurityContextPersistenceFilter
中取出了authResult,并存入了session
SecurityContextPersistenceFilter也是一个过滤器,它处于整个Security过滤器链的最前方,也就是说开始验证的时候是最先通过该过滤器,验证完成之后是最后通过。
获取认证用户信息
/**
* 获取当前登录的用户
* @return 完整的Authentication
*/
@GetMapping("/me1")
public Object currentUser() {
return SecurityContextHolder.getContext().getAuthentication();
}
@GetMapping("/me2")
public Object currentUser(Authentication authentication) {
return authentication;
}
/**
* @param userDetails
* @return 只包含了userDetails
*/
@GetMapping("/me3")
public Object cuurentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userDetails;
}
原文地址:https://blog.csdn.net/u013435893/article/details/79605239
SpringSecurity认证流程详解的更多相关文章
-
Kerberos认证流程详解
Kerberos是诞生于上个世纪90年代的计算机认证协议,被广泛应用于各大操作系统和Hadoop生态系统中.了解Kerberos认证的流程将有助于解决Hadoop集群中的安全配置过程中的问题.为此,本 ...
-
Linux启动流程详解【转载】
在BIOS阶段,计算机的行为基本上被写死了,可以做的事情并不多:一般就是通电.BIOS.主引导记录.操作系统这四步.所以我们一般认为加载内核是linux启动流程的第一步. 第一步.加载内核 操作系统接 ...
-
C++的性能C#的产能?! - .Net Native 系列《二》:.NET Native开发流程详解
之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...
-
[nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)
:由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...
-
迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解
本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...
-
iOS 组件化流程详解(git创建流程)
[链接]组件化流程详解(一)https://www.jianshu.com/p/2deca619ff7e
-
git概念及工作流程详解
git概念及工作流程详解 既然我们已经把gitlab安装完毕[当然这是非必要条件],我们就可以使用git来管理自己的项目了,前文也多多少少提及到git的基本命令,本文就先简单对比下SVN与git的区别 ...
-
Lucene系列六:Lucene搜索详解(Lucene搜索流程详解、搜索核心API详解、基本查询详解、QueryParser详解)
一.搜索流程详解 1. 先看一下Lucene的架构图 由图可知搜索的过程如下: 用户输入搜索的关键字.对关键字进行分词.根据分词结果去索引库里面找到对应的文章id.根据文章id找到对应的文章 2. L ...
-
JPEG图像压缩算法流程详解
JPEG图像压缩算法流程详解 JPEG代表Joint Photographic Experts Group(联合图像专家小组).此团队创立于1986年,1992年发布了JPEG的标准而在1994年获得 ...
随机推荐
-
在IOS输入框中 键盘上显示“搜索”
移动端web页面上使用软键盘时如何让其显示“前往”(GO)而不是换行?‘ 用一个 form 表单包裹住就会显示前往,单独的一个 input 就会提示换行.下面是测试地址: 有表单:https://js ...
-
Intellij Idea系列之JavaSE项目的创建(一)
Intellij Idea系列之JavaSE项目的创建(一) 一.Intellij Idea于 Intellij Idea是捷克的Jetbrain公司的一款优秀的针对Java程序员的IDE,其自从问世 ...
-
IIS中使用LocalDB遇到错误:error 50,Local Database Runtime error occurred.的解决办法
参见: [1] http://www.cnblogs.com/yjmyzz/archive/2009/10/26/1590033.html [2] http://blogs.msdn.com/b/sq ...
-
【英语】Bingo口语笔记(47) - 关于马的表达
beat a dead horse 浪费口舌
-
精通 VC++ 实效编程280例 - 01 窗口
窗口是屏幕上的一个矩形区域.窗口分为3种:重叠窗口.弹出窗口和子窗口.每个窗口都有由系统绘制的“非客户区”和应用程序绘制的“客户区”.在 MFC 中,CWnd 类为各种窗口提供了基类. 1 通过 HW ...
-
本地仓库有jar包maven依然报错的原因
本地Maven仓库有所需jar包依然报错,missing……………… 既然有这个jar包为什么还会报错呢? 找到本地仓库后发现里面有一个_remote.repositories文件 问题在_remot ...
-
Java API下载和查阅方法
使用来自API的类是简单的.只要把它当做自己写的就可以,采用import来引用,可以节省自己编程的气力~ 1.API文档下载地址 https://www.oracle.com/technetwork/ ...
-
Hadoop学习之路(十三)MapReduce的初识
MapReduce是什么 首先让我们来重温一下 hadoop 的四大组件: HDFS:分布式存储系统 MapReduce:分布式计算系统 YARN:hadoop 的资源调度系统 Common:以上三大 ...
-
Python 装饰器笔记
一.装饰器无参数 1.原函数无参数 def wrap_in_tag_b(fn): # wrap_in_tag_b 是真正的装饰器 def wrapped(): return "<b&g ...
-
thinkphp3.2.3版本在windows本地apache环境运行正常,上传到centos服务器apache环境中出现:thinkphp 上传根目录不存在!请尝试手动创建:uploads/
thinkphp 上传根目录不存在!请尝试手动创建:uploads/ ,是根目录下uploads权限问题,我把uploads权限改成了775,就可以上传图片了. chmod -R 775 u ...