经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发。但是前面章节的内容,属于让我们达到了 "会用" 的层级,但是 "为什么这么用",很多小伙伴就会一脸懵逼了。对于技术学习来说,我们追求的不仅要 "知其然",更要 "知其所以然"!
本篇文章中,壹哥 就跟各位小伙伴一起来了解剖析Spring Security源码内部,实现认证授权的具体过程及底层原理。接下来请各位做好心理准备,以下的学习过程可能会让你心理 “稍有不适” 哦,因为每次看源码都懵懵的......
一. Spring Security认证请求完整流程图在开始分析Spring Security关于认证授权的源码之前,壹哥先借用网上的一张图片,先让各位对认证授权有个大致的了解。
下图展示了从发起一个web请求,到经过内存或数据库层面的查询,最后再得到整个用户认证信息的全过程。
上图详细的给大家展示了Spring Security是如何实现认证授权的流程及运行原理的,并展示了Spring Security里的核心API。为了能让各位更好的理解掌握本文,请把这个图记下来哦!其实背诵下来也不难的,先这么背,然后那么背,一会就背下来了,hiahia......
在这个图中,涉及到了Spring Security关于认证授权的众多核心API,本文就是对这些核心API进行分析讲解,从而帮助各位掌握认证授权的执行流程,接下来请各位跟着 壹哥 来学习这些核心API吧。
因为Spring Security中关于认证授权相关的API很多,我先把这些API归为如下几类分别进行阐述,这样显得结构会更清晰一些。
二. 核心API之一:SecurityContextHolder、SecurityContext、Authentication 1. Authentication1.1 Authentication类关系
注:
实线箭头表示继承,虚线箭头表示实现;绿线表示接口与接口之间的关系,蓝线表示类与类之间的关系。
Authentication 是 spring-security-core 核心包中的接口,直接继承自 Principal 接口,而 Principal 是位于 java.security 包中,由此可知 Authentication 是 Spring Security 中的核心接口。Authentication接口中封装了用户信息,它有个很重要的子类UsernamePasswordAuthenticationToken,请各位先记住这个子类!
1.2 Authentication功能
在用户登录认证之前,用户名密码等信息 会被封装为一个 Authentication 的具体实现类对象。在登录认证成功之后又会生成一个信息更全面的Authentication对象,从该对象中可以 得到用户的权限信息列表、密码、用户细节信息、用户身份信息、认证信息等。这个 Authentication对象会被保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。
1.3 Authentication核心方法。
我们利用上面这些方法就可以获取到关于用户相关的信息,比如用户名、密码、角色等。
2. SecurityContext安全上下文,该类用于存储认证授权的相关信息,实际上就是存储 "当前用户" 的账号信息和相关权限,即代表当前用户信息的 Authentication 对象会被 SecurityContext 所持有(引用),SecurityContext上下文对象则被SecurityContextHolder 所持有(引用)。
2.1 SecurityContext类源码
下面是SecurityContext的源码内容,这个接口中只有两个方法,Authentication对象的getter、setter:
public interface SecurityContext extends Serializable {
// ~ Methods
// ========================================================================================================
/**
* Obtains the currently authenticated principal, or an authentication request token.
*
* @return the Authentication
or null
if no authentication
* information is available
*/
Authentication getAuthentication();
/**
* Changes the currently authenticated principal, or removes the authentication
* information.
*
* @param authentication the new Authentication
token, or
* null
if no further authentication information should be stored
*/
void setAuthentication(Authentication authentication);
}
从源码中可以得知,利用SecurityContext的getAuthentication()方法就可以拿到用户信息。
2.2 获取当前用户信息
那么我们如果是在自己的项目中,该如何获取当前已登录的用户信息呢?
因为Authentication身份信息与当前执行线程已经绑定在一起,所以我们可以使用以下代码在应用程序中获取当前已认证用户的用户名。
public String getCurrentUsername() {
//得到当前认证后的用户信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
if (principal instanceof Principal) {
return ((Principal) principal).getName();
}
return String.valueOf(principal);
}
3. SecurityContextHolder
3.1 SecurityContextHolder简介
由于一个请求从开始到结束都由一个线程处理,这个线程中途不会去处理其他的请求,所以在这段时间内,这个线程就相当于跟当前用户是一一对应的。SecurityContextHolder工具类就是用于把SecurityContext存储在当前线程中,在Web环境下,SecurityContextHolder是利用ThreadLocal来存储SecurityContext对象的。所以SecurityContextHolder可以用来设置和获取SecurityContext对象,该类主要是给框架内部使用,我们可以利用它获取当前用户的SecurityContext对象,进而进行请求检查,和访问控制等。
因为 SecurityContextHolder用于存储当前应用程序的SecurityContext安全上下文对象,而SecurityContext中则存储着当前正在访问系统的Authentication用户的详细信息,如当前操作的用户是谁,该用户是否已经被认证,拥有哪些角色权限等。所以我们可以用下图来展示SecurityContextHolder、SecurityContext与Authentication三者之间的关系:
3.2 SecurityContextHolder的线程安全性保障
默认情况下,SecurityContextHolder 使用 ThreadLocal 来保存 SecurityContext,这就意味着只要是同一线程中的方法,都可以从 ThreadLocal 中获取到当前的 SecurityContext对象。
因为Sevlet中的线程都是被池化复用的,一旦处理完当前的请求,这个线程可能马上就会被分配去处理其他的请求,而且也不能保证用户下次的请求会被分配到与上次相同的线程。也就是我们每次在请求完成后,Spring Security都会将 ThreadLocal 进行清除,即在每一次 请求 结束后都会自动清除当前线程的 ThreadLocal对象。
你可能会问,这时候如果我们的认证信息没有被保存,岂不是每次请求后都要重新认证登录?这明显不行,我们肯定要保存用户的认证信息!这个保存工作是由SecurityContextPersistenceFilter来完成的!
SecurityContextPersistenceFilter是Security中的一个拦截器,它的执行时机非常早,当请求来临时它会从SecurityContextRepository中把SecurityContext对象取出来,然后放入SecurityContextHolder的ThreadLocal中。在所有拦截器都处理完成后,再把SecurityContext存入SecurityContextRepository,并清除SecurityContextHolder内的SecurityContext引用。这样就实现了用户认证信息的保存,也保证了线程的安全性!
3.3 SecurityContextHolder中的方法及属性
在 SecurityContextHolder 中定义了一系列的静态方法,而这些静态方法的内部逻辑基本上都是通过 SecurityContextHolder 所持有的 SecurityContextHolderStrategy 对象来实现的,如 getContext()、setContext()、clearContext()等。如下图所示:
3.4 SecurityContextHolder中的3种存储策略
SecurityContextHolder中定义了3种策略 来管理SecurityContext对象的存储,默认使用的 strategy 是基于 ThreadLocal 的 ThreadLocalSecurityContextHolderStrategy,另外还有GlobalSecurityContextHolderStrategy 和 InheritableThreadLocalSecurityContextHolderStrategy 两种策略。
SecurityContextHolder可以通过设置来调整3种存储策略,三种策略详情如下:
- MODE_THREADLOCAL:表示将 SecurityContext对象 存储在当前线程中;
- MODE_INHERITABLETHREADLOCAL:表示将 SecurityContext对象 存储在线程中,但子线程可以获取到父线程中的 SecurityContext;
- MODE_GLOBAL:表示 SecurityContext对象内容 在所有线程中都相同。
SecurityContextHolder默认使用MODE_THREADLOCAL模式,即存储在当前线程中。一般而言,我们使用默认的 strategy 就可以了。
4. SecurityContextHolder、SecurityContext、Authentication总结经过以上3个小节的学习,下面我们来简单总结一下 SecurityContextHolder、SecurityContext 和 Authentication 这三个对象之间的关系。
SecurityContextHolder 用来保存 SecurityContext (安全上下文对象),利用SecurityContextHolder的getContext()方法可以得到SecurityContext对象;然后再通过调用 SecurityContext 对象中的getAuthentication()方法,就可以获取 Authentication 对象;最后我们利用Authentication对象,可以进一步获取已认证用户的详细信息。
三. 核心API之二:Filter相关API 1. Filter概念Spring Security 的底层是通过一系列的 Filter 来管理认证和授权请求的,形成了一组核心的过滤器链,每个 Filter 都有其自身的功能,而且各个 Filter 在功能上还有关联关系,项目启动后将会自动配置生效。其中最核心的就是 BasicAuthenticationFilter ,该Filter用来认证用户的身份。
接下来我们先简单讲解一下各个Filter及其执行顺序。
2. Filter顺序Spring Security 中定义的一些列Filter,不管实际应用中我们会用到哪些,它们都应当保持如下顺序。
- ChannelProcessingFilter:如果你访问的 channel 错了,那首先就会在 channel 之间进行跳转,如 http 变为 https。
- SecurityContextPersistenceFilter:在一开始进行 请求 的时候就会在 SecurityContextHolder 中建立一个 SecurityContext,然后在请求结束的时候,任何对 SecurityContext 的改变都会被 copy 到 HttpSession中。
- ConcurrentSessionFilter:因为它需要使用 SecurityContextHolder 的功能,而且更新对应 session 的最后更新时间,以及通过 SessionRegistry 获取当前的 SessionInformation 以检查当前的 session 是否已经过期,过期则会调用 LogoutHandler。
- 认证处理机制:如 UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter 等,以至于 SecurityContextHolder 可以被更新为包含一个有效的 Authentication 请求。
- SecurityContextHolderAwareRequestFilter:它将会把 HttpServletRequest 封装成一个继承自 HttpServletRequestWrapper 的 SecurityContextHolderAwareRequestWrapper,同时使用 SecurityContext 实现了 HttpServletRequest 中与安全相关的方法。
- JaasApiIntegrationFilter:如果 SecurityContextHolder 中拥有的 Authentication 是一个 JaasAuthenticationToken,那么该 Filter 将使用包含在 JaasAuthenticationToken 中的 Subject 继续执行 FilterChain。
- RememberMeAuthenticationFilter:如果之前的认证处理机制没有更新 SecurityContextHolder,并且用户请求包含了一个 Remember-Me 对应的 cookie,那么一个对应的 Authentication 将会设给 SecurityContextHolder。
- AnonymousAuthenticationFilter:如果之前的认证机制都没有更新 SecurityContextHolder 拥有的 Authentication,那么一个 AnonymousAuthenticationToken 将会设给 SecurityContextHolder。
- ExceptionTransactionFilter:用于处理在 FilterChain 范围内抛出的 AccessDeniedException 和 AuthenticationException,并把它们转换为对应的 Http 错误码返回或者对应的页面。
- FilterSecurityInterceptor:保护 Web URI,并且在访问被拒绝时抛出异常。
我们这个章节中暂时对过滤器链的执行流程不做详细描述,下一章节再做阐述。
四. 核心API之三:认证管理相关类 1. AuthenticationManagerAuthenticationManager的作用是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是抽象类,因此并不能实例化一个AuthenticationException异常并抛出,实际抛出的通常是其实现子类,如DisabledException、LockedException、BadCredentialsException等。AuthenticationManager的核心认证方法authenticate()如下所示:
AuthenticationManager是认证相关的核心接口,也是发起认证的起点。在实际开发中,我们可能会允许用户使用 用户名 + 密码 登录,同时允许用户使用 邮箱 + 密码,手机号码 + 密码 登录,甚至可能允许用户使用 指纹 登录,所以要求认证系统支持多种认证方式。几种不同的认证方式:用户名 + 密码(UsernamePasswordAuthenticationToken),邮箱 + 密码,手机号码 + 密码登录,可以分别对应三种 不同的 AuthenticationProvider 子类。
2. ProviderManagerSpring Security 中 AuthenticationManager 接口的默认实现类是 ProviderManager,但它本身并不直接处理身份认证请求,它会委托给内部配置的 AuthenticationProvider 列表providers。该列表会进行循环遍历,依次对比匹配以查看它是否可以执行身份验证,每个 Provider 验证程序在验证后,将会抛出异常或返回一个完全填充的 Authentication 对象。源码如下所示:
// org/springframework/security/authentication/ProviderManager.java
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
//维护一个AuthenticationProvider列表
private List providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?