SecurityContextHolder

SecurityContextHolder 持有的是安全上下文的信息,当前操作的用户是谁,用户是否已经被认证,他拥有哪些角色权限等,这些都被保存在 SecurityContextHolder 中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息,在 web 环境下,SpringSecurity 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息

public class SecurityContextHolder {

    // 三种工作模式的定义,每种工作模式对应一种策略
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL"; // 类加载时首先尝试从环境属性中获取所指定的工作模式
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy; // 初始化计数器, 初始为 0,
// 1. 类加载过程中会被初始化一次,此值变为 1
// 2. 此后每次调用 setStrategyName 会对新的策略对象执行一次初始化,相应的该值会增 1
private static int initializeCount = 0; static {
initialize();
} /**
* 清除上下文
*/
public static void clearContext() {
strategy.clearContext();
} /**
* 获取上下文
*/
public static SecurityContext getContext() {
return strategy.getContext();
} /**
* 获取计数器的值
*/
public static int getInitializeCount() {
return initializeCount;
} private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default, 设置缺省工作模式/策略 MODE_THREADLOCAL
strategyName = MODE_THREADLOCAL;
} if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
} else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
} /**
* 设置上下文
*/
public static void setContext(SecurityContext context) {
strategy.setContext(context);
} /**
* 设置工作模式
*/
public static void setStrategyName(String strategyName) {
SecurityContextHolder.strategyName = strategyName;
initialize();
} /**
* 获取对应工作模式的策略
*/
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
} /**
* 创建空的上下文信息
*/
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
} public String toString() {
return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
+ initializeCount + "]";
}
}

SecurityContext

安全上下文,主要持有 Authentication 对象,如果用户未鉴权,那么 Authentication 对象将会是空的

public interface SecurityContext extends Serializable {
/**
* 获取当前经过身份验证的主体,或身份验证请求令牌
*/
Authentication getAuthentication(); /**
* 更改当前经过身份验证的主体,或删除身份验证信息
*/
void setAuthentication(Authentication authentication);
}

Authentication

鉴权对象,该对象主要包含了用户的详细信息(UserDetails)和用户鉴权所需要的信息,如用户提交的用户名密码、Remember-me Token 或 digest hash 值等,按不同鉴权方式使用不同的 Authentication 实现

public interface Authentication extends Principal, Serializable {
//用来获取用户的权限。
Collection<? extends GrantedAuthority> getAuthorities();
//用来获取用户凭证,一般来说就是密码。
Object getCredentials();
//用来获取用户携带的详细信息,可能是当前请求之类的东西。
Object getDetails();
//用来获取当前用户,可能是一个用户名,也可能是一个用户对象。
Object getPrincipal();
//判断当前用户是否认证成功。
boolean isAuthenticated();
//设置用户是否认证成功
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

GrantedAuthority

表示了当前用户所拥有的权限(或角色)信息,这些信息由授权负责对象 AccessDecisionManager 来使用,并决定最终用户是否可以访问某资源(URL 或方法调用或域对象),鉴权使并不会使用到该对象

public interface GrantedAuthority extends Serializable {
//获取当前用户所拥有的权限(或角色)信息
String getAuthority();
}

UserDetailsService

提供一个接口 loadUserByUsername(String username),一般通过扩展该接口显式获取我们的用户信息,用户登陆时传递的用户名和密码也是通过这里查找出来的用户名和密码进行校验,真正的校验由 AuthenticationManager 和 AuthenticationProvider 负责的。如果用户不存在时应返回 NULL,而是抛出异常 UsernameNotFoundException

public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

UserDetails

规范了用户详细信息所拥有的字段,如用户名、密码、账号是否过期、是否锁定等,在 SpringSecurity 中,获取当前登录的用户的信息,一般情况是需要在该接口上面进行扩展

public interface UserDetails extends Serializable {
// 返回权限集合
Collection<? extends GrantedAuthority> getAuthorities();
// 获取密码
String getPassword();
// 获取用户名
String getUsername();
// 判断用户是否未过期
boolean isAccountNonExpired();
// 判断账户是否未锁定
boolean isAccountNonLocked();
// 判断用户凭证是否没过期,即密码是否未过期
boolean isCredentialsNonExpired();
// 判断用户是否可用
boolean isEnabled();
}

获取用户信息

// 获取安全上下文对象,就是那个保存在 ThreadLocal 里面的安全上下文对象
// 总是不为 null(如果不存在,则创建一个 authentication 属性为 null 的 empty 安全上下文对象)
SecurityContext securityContext = SecurityContextHolder.getContext(); // 获取当前认证了的 principal(当事人), 或者 request token (令牌)
// 如果没有认证,会是 null, 该例子是认证之后的情况
Authentication authentication = securityContext.getAuthentication() // 获取当事人信息对象,返回结果是 Object 类型,但实际上可以是应用程序自定义的带有更多应用相关信息的某个类型。
// 很多情况下,该对象是 Spring Security 核心接口 UserDetails 的一个实现类,你可以把 UserDetails 想像
// 成我们数据库中保存的一个用户信息到 SecurityContextHolder 中 Spring Security 需要的用户信息格式的
// 一个适配器。
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

安全身份认证流程

过滤器链

当初始化 Spring Security 时,在 org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration 中会往 Spring 容器中注入一个名为 SpringSecurityFilterChain 的 Servlet 过滤器,类型为 org.springframework.security.web.FilterChainProxy。它实现了 javax.servlet.Filter,因此外部的请求都会经过这个类,而 FilterChainProxy 是一个代理,真正起作用的是 FilterChainProxy 中 SecurityFilterChain 所包含的各个 Filter

  1. SecurityContextPersistenceFilter

这个 Filter 是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext

  1. UsernamePasswordAuthenticationFilter

用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变

  1. FilterSecurityInterceptor

是用于保护 web 资源的,使用 AccessDecisionManager 对当前用户进行授权访问

  1. ExceptionTranslationFilter

能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出

认证流程

  1. Spring Security 定义了一个过滤器链,当认证请求到达这个链时,该请求将会穿过这个链条用于认证和授权,这个链上可以定义 1~N 个过滤器,过滤器的用途是获取请求中的认证信息,根据认证方法进行路由,把认证信息传递给对应的认证处理程序进行处理,不同的过滤器处理不同的认证信息
  • HTTP Basic 认证通过过滤器链,到达 BasicAuthenticationFilter
  • HTTP Digest 认证被 DigestAuthenticationFilter 识别,拦截并处理
  • 表单登录认证被 UsernamePasswordAuthenticationFilter 识别,拦截并处理
  1. 基于用户凭证创建 AuthenticationToken

如:用户在登录表单中输入用户名和密码,并点击确定,浏览器提交 POST 请求到服务器,穿过过滤器链,被 UsernamePasswordAuthenticationFilter 识别,UsernamePasswordAuthenticationFilter 提取请求中的用户名和密码来创建 UsernamePasswordAuthenticationToken 对象

  1. 把组装好的 AuthenticationToken 传递给 AuthenticationManager

如:组装好的 UsernamePasswordAuthenticationToken 对象被传递给 AuthenticationManager 的 authenticate 方法进行认证决策,AuthenticationManager 只是一个接口,实际的实现是 ProviderManager

  1. ProviderManager 委托给 AuthenticationProvider 进行认证处理

AuthenticationProvider 提供了不同的实现类,ProviderManager 会把收到的 UsernamePasswordAuthenticationToken 对象传递给列表中的每一个 AuthenticationProvider 进行认证,那 UsernamePasswordAuthenticationToken 会被哪一个接收和处理呢?是由 supports 方法来决定的

  1. UserDetailsService 获取用户信息

例如:DaoAuthenticationProvider 通过 UserDetailsService 查找对应的用户信息

  1. 认证结果处理

例如:如果认证成功(用户名和密码完全正确),AuthenticationProvider 将会返回一个完全有效的 Authentication 对象(UsernamePasswordAuthenticationToken),否则抛出 AuthenticationException 异常

认证完成后,AuthenticationManager 将会返回该认证对象(UsernamePasswordAuthenticationToken)返回给过滤器

  1. 存储认证对象

相关的过滤器获得一个认证对象后,把他存储在安全上下文中(SecurityContext)用于后续的授权判断

SpringSecurity5(13-核心组件和认证流程)的更多相关文章

  1. SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

    前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...

  2. [转]Spring Security Oauth2 认证流程

    1.本文介绍的认证流程范围 本文主要对从用户发起获取token的请求(/oauth/token),到请求结束返回token中间经过的几个关键点进行说明. 2.认证会用到的相关请求 注:所有请求均为po ...

  3. SpringSecurity认证流程详解

    SpringSecurity基本原理 在之前的文章<SpringBoot + Spring Security 基本使用及个性化登录配置>中对SpringSecurity进行了简单的使用介绍 ...

  4. CWMP开源代码研究4——认证流程

    TR069 Http Digest 认证流程   一 流程及流程图 1.1盒端主动发起Http Digest认证流程  盒端CPE                                    ...

  5. Kerberos认证流程详解

    Kerberos是诞生于上个世纪90年代的计算机认证协议,被广泛应用于各大操作系统和Hadoop生态系统中.了解Kerberos认证的流程将有助于解决Hadoop集群中的安全配置过程中的问题.为此,本 ...

  6. Shiro第二篇【介绍Shiro、认证流程、自定义realm、自定义realm支持md5】

    什么是Shiro shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. spring中有spring security (原名Acegi),是一个权限框架,它和sp ...

  7. 最简单易懂的Spring Security 身份认证流程讲解

    最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...

  8. ASP.NET Forms 认证流程

    ASP.NET Forms 认证 Forms认证基础 HTTP是无状态的协议,也就是说用户的每次请求对服务器来说都是一次全新的请求,服务器不能识别这个请求是哪个用户发送的. 那服务器如何去判断一个用户 ...

  9. Spring Security构建Rest服务-0701-个性化用户认证流程

    上一篇说了用户认证的基本流程,但是上一篇当访问一个受保护的服务后,如果未认证会调到默认的登录页面,这样是不行的,而且认证成功后,就直接访问了那个服务,如果想要做认证成功后做一些操作,还需要自定义. 个 ...

  10. 二、django rest_framework源码之认证流程剖析

    1 绪言 上一篇中讲了django rest_framework总体流程,整个流程中最关键的一步就是执行dispatch方法.在dispatch方法中,在调用了一个initial方法,所有的认证.权限 ...

随机推荐

  1. weblogic-copy

    一.简介 WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发.集成.部署和管理大型分布式Web ...

  2. 在OERV也可以玩MC(下)

      话接上回,上期讲述了在OERV安装HMCL的历程,这期讲讲HMCL的打包.   Show openEuler:24.09 / HMCL - 开源软件构建与测试.在这个网站里,可以看到有好几个文件, ...

  3. FunPapers[1]: GBDT和DNN强强联手,表格预测新突破!

    Team up GBDTs and DNNs: Advancing Efficient and Effective Tabular Prediction with Tree-hybrid MLPs h ...

  4. S3基准测试工具 - Warp使用简介

    本文分享自天翼云开发者社区<S3基准测试工具 - Warp使用简介>,作者:y****n 1.Warp⼯具简介 warp 是⼀款开源的S3基准测试⼯具,开源S3项⽬minio下的⼀个⼦项⽬ ...

  5. idea遇见Command line is too long. Shorten command line for Main or also for Application default configuration?

    <property name="dynamic.classpath" value="true" /> 第一步:找到项目目录下的.idea\works ...

  6. ECharts中实现x轴中的坐标以不同间距显示的效果

    1.需求描述 我们日常在使用ECharts实现曲线图或柱状图的时候,x轴上的坐标都是等距离显示的. 有时候我们可能有这个需求: x轴上的坐标距离按照对应数据的比例进行显示. 打个比方,假设x轴上有5个 ...

  7. 什么是Kappa架构?

    一.简介 相当于在Lambda架构上去掉了批处理层(Batch Layer),只留下单独的流处理层(Speed Layer).通过消息队列的数据保留功能,来实现上游重放(回溯)能力. 当流任务发生代码 ...

  8. Hetao P1391 操作序列 题解 [ 绿 ] [ 二维线性 dp ]

    操作序列:简单的二维 dp. 观察 我们每次操作可以让 \(x\) 变为 \(2x-1\),或者当 \(x\) 为奇数时让 \(x\) 变为 \(\frac{x+1}{2}\). 显然,执行第一种操作 ...

  9. 多项式算法再探:FMT 和 FWT

    我们知道,FFT 和 NTT 可以用来解决下面这种问题: \[c_k=\sum_{i+j=k}a_ib_j \] 不过,这并不是卷积的全部形态,比如下面这种: \[c_k=\sum_{i*j=k}a_ ...

  10. Sqoop - [01] 概述

    将关系型数据库(Oracle.MySQL.PG等)数据与Hadoop数据进行转换的工具. 一.Sqoop1和Sqoop2的区别 Sqoop1由client端直接接入Hadoop,任务通过解析生成对应的 ...