Spring Security OAuth2.0(5):Spring Security工作原理

news2024/10/5 22:23:57

文章目录

  • 工作原理
    • 结构总览
  • 认证流程
  • 授权流程
  • AuthenticationProvider
  • UserDetailsService
  • PasswordEncoder
    • 如何使用BCryptPasswordEncoder
  • 授权流程
    • 授权流程
    • 授权决策

工作原理

结构总览

\qquad Spring Security 所解决的问题就是安全访问控制,而安全访问控制功能其实就是所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。Spring Security 对Web资源的保护是通过Filter入手的,所以从这个Filter入手,逐步深入Spring Security原理。
$\qquad%当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下面是Spring Security过滤器链结构图。
在这里插入图片描述
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理,下图是FilterChainProxy相关类的UML图示。
在这里插入图片描述
spring security功能的实现主要是由一系列过滤器链相互配合完成。、
在这里插入图片描述

下面价绍过滤器链中主要的几个过滤器和其作用

  • SecurityContextPersistenceFilter
    这个Filter是整个拦截国车过的入口和出口,会在请求开始时从配置好的SecurityContextRepository中获取SecurityContext,然后把它设置给SecurityContextHolder。在请求完成后将SecurityContextHolder持有的SecurityContext再保存到配置好的SecurityContextRepository,同时清除SecurityContextHolder所持有的SecurityContext;
  • UsernamePasswordAuthenticationFilter
    用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的AuthenticationSuccessHandler和AuthenticationFailureHandler,这些都是可以根据需求做出相关改变;
  • FilterSecurityInterceptor
    用于保护Web资源的,使用AccessDecisionManager对当前用户进行授权访问。
  • ExceptionTranslationFilter
    能够捕获来自FilterChain所有的一场,并进行处理。但是它只会处理两类异常;AuthenticationException和AccessDeniedException,其它的异常它会继续抛出。

认证流程

在这里插入图片描述
认证过程:

  1. 用户提交用户名、密码被SecurityFilterChain中的UsernamePassowordAuthenticationFilter过滤器获取到,封装为请求的Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
  2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
  3. 认证成功后,AuthenticationManager身份管理器返回一个被填满信了信息的Authentication(包含了全新信息,身份信息,细节信息,但是密码通常会被移除)实例。
  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication()方法,设置到其中。
    可以看到AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。

授权流程

Spring Security 可以通过http.authorizeRequests()对web请求进行授权保护。Spring Security 使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
在这里插入图片描述

AuthenticationProvider

\qquad 通过前面的Spring Security认证流程可以知,认证管理器(AuthenticationManager)委托AuthenticationProvider完成认证工作。
AuthenticationProvider是一个接口,定义如下

public interface AuthenticationProvider {
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);
}

authenticate()方法定义了认证的实现过程,它的参数一个Authentication,里面包含了登录用户所提交的用户、密码等。而返回值也是一个Authentication,这个Authentication则是在认证成功后,将用户的权限及其他信息重新组装后生成。
\qquad Spring Security中维护着一个List列表,存放着多种认证方式,不同的认证方式使用不同的AuthenticationProvider。如使用用户名密码登录时,使用AuthenticationProvider1,短信登陆时使用AuthentcationProvider2等这样的例子。
\qquad 每个AuthenticationProvider需要实现 supports()方法来表明自己支持的认证方法,如我们使用表单方式认证,在提交请求时Spring Security会生成UsernamePasswordAuthenticationToekn,它是一个Authentication,里面封装着用户提交的用户名、密码信息。而对应着,哪个AuthenticationProvider来处理它?
我们在DaoAuthenticationProvider的基类AbstractUserDetailsAuthenticationProvider发现一下代码

public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

也就是说当web表单提交用户名密码时,Spring Security由DaoAuthenticationProvider处理。

\qquad 最后,我们来看一下Authentication(认证信息)的结构,它是一个接口,我们之前提到的UsernamePasswordAuthenticationToken就是它的实现之一:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();//权限列表

    Object getCredentials();//凭证

    Object getDetails();//用户信息

    Object getPrincipal();//用户身份

    boolean isAuthenticated();//是否用户认证通过

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

UserDetailsService

实现UserDetailsService接口

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

很多人把DaoAuthenticationProvider和UserDetailsService的职责搞混淆,其实UserDetailsService只负责从特定的地方(数据库)加载用户信息,仅此而已。而DaoAuthenticationProvider的职责更大,它完成完整的整个认证流程,同时会把UserDetails填充Authentication。
UserDetails是什么?

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

我们知道了它的结构,实现自己的实现类

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
    //根据用户名获取用户的信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //从数据库中查用户信息
        System.out.println("查询用户username=" + username);
        UserDetails userDetails = User.withUsername("xiaowang").password("111").authorities("p1").build();
        return userDetails;
    }
}

同时屏蔽掉内存定义的用户
在这里插入图片描述
启动服务器测试登录,可以看到它实际调用了我们自定义的UserDetailsService
在这里插入图片描述
在这里插入图片描述

PasswordEncoder

自定义解析器。
\qquad DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,它是如何与请求Authentication中的密码做对比的?
\qquad 这里Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过PasswordEncoder接口的matches方法进行密码的对比,而具体的密码对比细节取决于实现:

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

而Spring Securiy提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如下声明即可,如下:

 //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() { //原文密码比较
        return NoOpPasswordEncoder.getInstance();
    }

NoOpPasswordEncoder 采用字符串匹配方法,不对密码进行加密比较处理,密码比较流程如下:

  1. 用户输入密码(明文)
  2. DaoAuthenticationProvider获取UserDetails(其中存储了用户的正确密码)
  3. DaoAuthenticationProvider使用PasswordEncoder对输入的密码和正确的密码进行校验,密码一致则通过校验,否则校验失败。
    \qquad NoPasswordEncoder的校验规则拿输入的密码和UserDetails中的正确密码进行字符串比较,字符串内容一致则校验成功,否则校验失败。

实际项目中推荐使用BCryptPasswordEncoder,Pkbdf2PasswordEncoder,ScrypePasswordEncoder等。

如何使用BCryptPasswordEncoder

BCryptPasswordEncoder并不需要引入新的依赖

在安全类中定义BCryptPasswordEncoder

 //使用BCryptPasswordEncoder密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

为了方便测试,这里需要使用BCrypt生成一个密码

由于加盐,所以每次生成的都是不同的密码,但是并不妨碍它的校验。

@RunWith(SpringRunner.class)
public class BcryptTest {

    @Test
    public void testBcyrpt() {
        //BCrypt.gensalt() 生成盐
        String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(hashpw);

        //校验
        boolean checkpw = BCrypt.checkpw("123", "$2a$10$NcYCXQUjgeCzc2NWWop6s.pz6KCW9QMaLkBYKu34Co38KTJ3ef2jW");
        System.out.println(checkpw);
    }
}

在这里插入图片描述
在这里插入图片描述

把用户的密码替换
在这里插入图片描述
再次启动测试登录
在这里插入图片描述

授权流程

授权流程

\qquad 通过前面我们知道,Spring Security 可以通过http.authorizeRequests()对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
在这里插入图片描述
分析授权流程:

  1. 拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的FilterSecurityInterceptor的子类拦截。
  2. 获取资源访问策略,FilterSecurityInterceptor会从SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource获取要访问当前资源所需要的权限Collection<ConfigAttribue>
    SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则,读取访问策略如:
http.authorizeRequests()
                .antMatchers("/r/r1").hasAnyAuthority("p1")
                .antMatchers("/r/r2").hasAnyAuthority("p2")
  1. 最后,FilterSecurityInterceptor会调用AccessDecisionManager进行授权决策,如果决策通过,则允许访问资源,否则将禁止访问。

AccessDecisionManager(访问决策管理器)的核心接口如下:

public interface AccessDecisionManager {
    void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);
}

decide参数解释
authentication: 要访问资源的访问者的身份
object: 要访问的受保护资源,web请求对应FilterInvocation
configAttributes: 是受保护资源的访问策略,通过SecurityMetadataSource获取

授权决策

AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。
在这里插入图片描述
\qquad 通过上面可以看出,AccessionDecisionManager中包含的一系列AccessDecisionVoter将会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
AccessionDecisonVoter是一个接口,其中定义有三个方法,具体结构如下所示。

public interface AccessDecisionVoter<S> {
    int ACCESS_GRANTED = 1;
    int ACCESS_ABSTAIN = 0;
    int ACCESS_DENIED = -1;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);

    int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3);
}

vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。ACCESS_GRANTED表示同意,ACCESS_DENIED表示拒绝,ACCESS_ABSTAIN表示弃权。如果一个AccessDecisionVoter不能判定当前Authentication是否拥有访问对应受保护对象的权限,则其vote()方法的返回值应当为弃权ACCESS_ABSTAIN 。
\qquad Spring Security内置了三个基于投票的AccessDecisionManager实现类如下,它们分别是AffirmativeBasedConsensusBasedUnanimousBased
\qquad AffirmativeBased的逻辑是
(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
(2)如果全部弃权也表示通过;
(3) 如果没有一个投赞成票,但是有人投反对票,则将抛出AccessDeniedException。

Spring Security默认使用的是AffirmativeBased。
\qquad ConsensusBased的逻辑是:
(1)如果赞成票多于反对票则表示通过。
(2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException
(3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualsGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualsGrantedDeniedDecisions的值默认为true。
(4)如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

\qquad UnanimousBased的逻辑与另外两种实现有些不一样,另外两种都会一次性把保护对象的配置属性全部传递给AccessDecisionVoter进行投票,而UnanimousBased会一次值传递一个ConfigAttribute给AccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousdBased中其投票结果就不一定是赞成了。
UnanimousBased的逻辑具体来说是这样的:
(1)如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException。
(2)如果没有反对票,但是有赞成票,则表示通过。
(3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则表示通过,false则抛出AccessDeniedException。
\qquad Spring Security内置了一些投票者实现类如RoleVoter、AuthenticatedVoter、WebExpressionVoter等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/768244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.12 Bootstrap 超大屏幕(Jumbotron)

文章目录 Bootstrap 超大屏幕&#xff08;Jumbotron&#xff09; Bootstrap 超大屏幕&#xff08;Jumbotron&#xff09; 下面将讲解 Bootstrap 支持的另一个特性&#xff0c;超大屏幕&#xff08;Jumbotron&#xff09;。顾名思义该组件可以增加标题的大小&#xff0c;并为登陆…

MySQL-多表设计-一对一多对多

一对一 案例&#xff1a;用户 与身份证信息 的关系关系&#xff1a;一对一关系&#xff0c;多用于单表拆分&#xff0c;将一张表的基础字段放在一张表中&#xff0c;其它字段放在另一张表中&#xff0c;以提高操作效率实现&#xff1a;在任意一方加入外键&#xff0c;关联另一…

Linux小程序:倒计时和进度条

Linux小程序 在Linux中我们实现两个小程序来体会\r和\n的区别&#xff0c;以及缓冲区是什么&#xff1f; 文章目录 Linux小程序前言回车和换行的区别缓冲区 小程序的实现倒计时程序进度条程序 总结 前言 回车和换行的区别 对于 \r 和 \n 的理解&#xff1a; \n 表示换行且回…

创建git仓库连接上传全过程记录

1.初始化仓库 使用git init命令在一个新文件夹里初始化仓库 2.在github创建仓库 3.连接git仓库 采用命令是 git remote add origin 仓库地址4.添加文件进行测试 5.选择要上传的文件 一般选择git add .命令 6.提交文件到本地仓库 git commit -m "备注信息"7.…

RISCV - 1 RV32/64G指令集清单

RISCV - 1 RV32/64G指令集清单 1 RV32/64G指令类型2 RV32I 基本指令集3 RV64I基础指令集&#xff08;除了RV32I)4 RV32/RV64 Zifencei标准扩展5 RV32/RV64 Zicsr标准扩展6 RV32M标准扩展7 RV64M标准扩展&#xff08;除了RV32M)8 RV32A标准扩展9 RV64A标准扩展&#xff08;除了R…

php实现站群软件权限管理功能示例

1.管理员页面RBAC.php <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>权限管理</title> <script src"bootstrap/js/jquery-1.11.2.min.js"></script> </head>…

jenkins war包 centos启动安装指导

文章目录 步骤1&#xff1a;进入官网&#xff0c;下载到Jenkins的war包1.1 放置在指定位置1.2 放置安装包和创建文件放置路径1.3 检查环境1.4 配置启动命令和结束命令 步骤2&#xff1a; 启动后进入到Jenkins页面2.1 安装插件&#xff0c;例如流水线2.2 依然出现安装插件失败的…

ReWorks系统加载启动

1、通过网络或本地加载启动 配置tftp网络 网卡属性配置为100Mbps全双工 配置串口 目标板上电进入uboot 设置PC机IP、目标机IP、目标机MAC地址 加载文件并启动 固化系统镜像至SD卡 需支持挂载文件系统&#xff0c;并启动ftp服务 选择SD卡、文件系统、ftp服务 挂接SD卡 也可以…

动态规划---最大字段和

描述 给出 n 个整数序列&#xff08;可能为负数&#xff09;组成的序列 a1​, a2​,...,an​&#xff0c;求该序列形如 的子段和的最大值。当所有整数均为负数时&#xff0c;定义最大子段和为 0 。 输入描述 多测试用例。每个测试用例 2 行&#xff1a; 第一行是序列的个数…

Day977.除了授权码许可类型,OAuth 2.0还支持什么授权流程? -OAuth 2.0

除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f; Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f;的内容。 授权码许可的流程最完备、最安全没错儿&#xff0c;但它…

将大模型集成到语音识别系统中的例子

概述 本文旨在探索将大型语言模型&#xff08;LLMs&#xff09;集成到自动语音识别&#xff08;ASR&#xff09;系统中以提高转录准确性的潜力。 文章介绍了目前的ASR方法及其存在的问题&#xff0c;并对使用LLMs的上下文学习能力来改进ASR系统的性能进行了合理的动机论证。 本…

【分布式缓存】springboot整合jetcache使用详解

目录 一、前言 二、多级缓存问题 2.1 缓存分类 2.1.1 本地缓存 2.1.2 分布式缓存 2.2 独立缓存的问题 2.2.1 缓存雪崩问题 2.2.2 对宽带压力大 2.2.3 运行效率低 2.3 多级缓存方案 2.3.1 多级缓存实践方案推荐 三、jetcache介绍 3.1 jetcache概述 3.2 jetcache 特…

手写代码系列

(1)手写clearfix .clearfix:after{content:; display:table;clear:both;} (2) 手写圣杯模型 (3)手写深拷贝 递归 const obj3={age:20,name:xxx,address:{} }, arr:[a,b,c] function deeepClone(obj={}){} (4)手写画图解释原型链(class的原型和本质)

vue3 引入dataV 报错,使用patch-package记录插件包 node_modeule 修改记录。 vite 版DataV

开发数字大屏功能&#xff0c;引用dataV UI组件库比较好用&#xff0c;目前分为Vue2 和 Vue3 两个版本。 Vue2 --DataV版本 yarn add jiaminghi/data-viewVue3 --DataV版本 yarn add dataview/datav-vue3vite – --DataV版本 //不想动手改的&#xff0c;也可以使用此版本&a…

2、常用布局控件

首先,展开工具箱。注意这里打开的文件要是窗体文件,就是Form1,cs,否则工具箱列表将是空的。 然后选到容器,这里我们就可以看到常用的布局控件了。 使用的时候直接从左边拉到右边即可 注意:布局是支持嵌套的。 这里我们逐个介绍。 第一个是指针,这个不是布局控件,就是…

LCD-STM32液晶显示中英文-(7.字模及显示原理)

目录 字模介绍 什么是字模 字模的构成 字模显示原理 字模制作 如何制作字模 字模寻址公式 存储字模文件 字模介绍 什么是字模 有了编码&#xff0c;我们就能在计算机中处理、存储字符了&#xff0c;但是如果计算机处理完字符后直接以编码的形式输出&#xff0c;人类将难…

python解析器和pycharm编译器安装

python解析器下载地址&#xff1a;https://www.python.org/getit/ 注意事项&#xff1a; 1. 建议下载3.6以以上的版本&#xff0c; 2. 官网下载比较慢&#xff0c;可以自行寻找其它网站下载&#xff0c; 3. 建议使用.exe安装包方式下载安装 下载完成后双击运行 验证是否安装成功…

使用Python提取TripAdvisor数据:探索旅游的新途径

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 猫途鹰&#xff08;TripAdvisor&#xff09;是一个旅游点评网站&#xff0c;如果您想要爬取该网站的数据&#xff0c;需要了解该网站的访问规则和爬取限制。 环境使用: Python 3.8 Pycharm 代码实现 针对猫途鹰网站&#…

Echarts柱状图横向滚动,如何实现从后往前滚动

Echarts柱状图横向滚动&#xff0c;如何实现从后往前滚动 设置开始和结束的横坐标&#xff0c;设置产生横向滚动条

解决apkanalyzer.bat could NOT be found in D:\Download\Android SDK Tools!警告报错

appium安装过程中很可能出现以下警告报错&#xff0c;咱就按如下操作即可搞定&#xff01;&#xff01;&#xff01; apkanalyzer.bat could NOT be found in D:\Download\Android SDK Tools! 一、下载Command line tools 下载地址&#xff1a;​https://developer.android.g…