【个人版】SpringBoot下Spring-Security核心概念解读【二】

Spring-Security + HttpSecurity

Spring-Security全局导读:
1、Security核心类设计
2、HttpSecurity结构和执行流程解读
3、Spring-Security个人落地篇

背景: Spring-Security框架的核心架构上一篇已经概述,展示其执行流程及逻辑,但是和我们实际使用有点差距,相信大家在使用此框架时,肯定被以下代码迷惑过:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin().disable()
                .authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
                .and().logout().logoutSuccessHandler(authResult).permitAll()
                .and().exceptionHandling().authenticationEntryPoint(authResult);
    }
}

上述代码直接看就是一串httpSecurity对象的链式配置,但很多问题没有还需明确
1、HttpSecurity可以设置哪些配置点? 重点配置有哪些?
2、链条中为什么需要add方法?是否必须?
3、每个配置点底层以何种形式存在?执行时机或顺序如何定义?
4、HttpSecurity除了链式配置外,还需要哪些配置?
5、在适配模式下使用,重写的configure方法何时执行?
6、HttpSecurity在何处以何种方式被使用的?

下面将在以上问题的基础上,对HttpSecurity做自我理解后的解读:
基础条件:
1、SpringBoot的Web项目【版本2.7.14】
2、集成相关spring-security的starter依赖

解答一、HttpSecurity的由来
上下文自动配置类:
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration

引入Security注解开关:
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
传递关系(依次由上到下):

  • SecurityAutoConfiguration
  • SpringBootWebSecurityConfiguration
  • WebSecurityEnablerConfiguration
  • EnableWebSecurity

进而引入Security框架核心类的配置类:
AuthenticationConfiguration => AuthenticationManager管理类
WebSecurityConfiguration => WebSecurity配置类
HttpSecurityConfiguration => HttpSecurity配置类

HttpSecurity创建代码简化版:

@Configuration(
    proxyBeanMethods = false
)
class HttpSecurityConfiguration {
    private ObjectPostProcessor<Object> objectPostProcessor; // 框架类初始化增强类,后续单独讲
    private AuthenticationManager authenticationManager; // 授权管理器,核心依赖类,可自定义,可系统自动配置,后续单独讲
    private AuthenticationConfiguration authenticationConfiguration; // 授权管理器配置类,作为父授权管理器托底
    private ApplicationContext context;

	@Scope("prototype")
    @Bean({"org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity"})
    HttpSecurity httpSecurity() throws Exception {
        WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(this.context);
        // 创建授权管理器构建类,parent设置时可以全局搜索我们自定义的AuthenticationProvider类,就不用通过构建器手工设置
		AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);
        authenticationBuilder.parentAuthenticationManager(this.authenticationManager());
		// 实例化HttpSecurity
        HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, this.createSharedObjects());
        // 初始化HttpSecurity安全管理逻辑,如logout、登录页、异常等场景设置,都是默认配置,做到开箱即用
		http.csrf(Customizer.withDefaults()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling(Customizer.withDefaults()).headers(Customizer.withDefaults()).sessionManagement(Customizer.withDefaults()).securityContext(Customizer.withDefaults()).requestCache(Customizer.withDefaults()).anonymous(Customizer.withDefaults()).servletApi(Customizer.withDefaults()).apply(new DefaultLoginPageConfigurer());
        http.logout(Customizer.withDefaults());        
		this.applyDefaultConfigurers(http); // SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader) -- 默认factories加载实现类,忽略	
        return http;
    }

    private AuthenticationManager authenticationManager() throws Exception {
        return this.authenticationManager != null ? this.authenticationManager : this.authenticationConfiguration.getAuthenticationManager();
    }

	// HttpSecurity内部全局共享实例集合,核心类都会在此保存并重复使用
    private Map<Class<?>, Object> createSharedObjects() {
        Map<Class<?>, Object> sharedObjects = new HashMap();
        sharedObjects.put(ApplicationContext.class, this.context);
        return sharedObjects;
    }
}

除代码中标注的注释外,还需要注意:
1、注意HttpSecurity上@Bean发布范围是多例,因为可以Proxy下可以配置多个过滤器链(注:如果在不同类或同一个类的不同地方多次引用,相关配置会错乱,除非自己另行全局封装)
2、AuthenticationManager是部分Filter的核心依赖,尤其涉及我们自定义鉴权部分的代码,其主旨为拦截后对接口具体的参数处理与返回,也就是我们的鉴权逻辑。

解答二:HttpSecurity结构之配置与构建过程
作为一个框架的核心类,HttpSecurity的设计完成应用了多个巧妙的设计模式,使代码可读性、复用性更高。
核心设计模式一:构建器模式
构建器模式是最常见的创建型模式,在类相关实例变量相当多的场景下非常实用,避免长列表或多重载构造函数。在Security框架中,安全策略多样,每个安全策略需要管理的参数量不固定且灵活性较高(隐含意思是:HttpSecurity配置安全策略及安全策略配置具体参数都适用此场景,实际security框架就是这么设计和干的),如果采用常见if…else…语法,配置及使用过程将毫无人性,构建器模式让HttpSecurity的链式配置更加易读和使用。

Spring6中将配置器的创建改成lambda形式,进一步缩短HttpSecurity的配置层次,将配置器的创建与HttpSecurity的配置解耦,这就是框架的魅力。

构建器模式代码结构很简单,难点在于安全知识点的理解与调控,开发过程中注意参数变化导致的配置器的失效问题,下面将简单列举来进一步说明:
在这里插入图片描述
1、图中1处的logout无参方法返回配置器Configurer的实例对象,我们通过配置内部构建方法进一步配置其他参数值,结束配置可用HttpSecurity另起一行配置新的配置器,或者使用logout配置器的and方法继续链式配置其他配置器
2、图中2处的logout有参方法提供了一个自定义构造器参数,可以对系统默认的Configurer实例做进一步配置,可对象可由lambda表达式代替,Spring6已经全面像此场景靠拢(部分人对过长的HttpSecurity链无感)

配置器操作中其实隐含一个非常重要的操作,可以看到图中的getOrApply方法。该方法一方面将Configurer实例添加到HttpSecurity的全局缓存中,并将HttpSecurity的实例对象的引用传递进原Configurer内部,做到相交相融,还有将Configurer从普通new出的实例进一步提级,完成类似spring容器里的实例对象的生命周期的管理。根据反馈效果,后续可追加解读。

核心设计模式二:模板模式
模板模式常用在继承结构,HttpSecurity继承了AbstractConfiguredSecurityBuilder -> AbstractSecurityBuilder抽象类,该抽象类将构建方法完全委托给实现类完成,已知实现类有:HttpSecurity、WebSecurity、AuthenticationManagerBuilder,可以看到这三个类正是Security框架的核心。模板模式特点之xxx -> doXxx,构建过程如下:

protected final O doBuild() throws Exception {
    synchronized(this.configurers) {
        this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
        this.beforeInit();
        this.init();
        this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
        this.beforeConfigure();
        this.configure();
        this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
        O result = this.performBuild();
        this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
        return result;
    }
}

构建流程说明:
1、beforeInit()

  • 尴尬,三个实现类都忽略了

2、init()

  • 执行所有安全配置器的初始化代码,即configurer.init(HttpSecurity)

3、beforeConfigure()

  • 授权管理器实例化并添加到sharedObjects集合,AuthenticationManagerBuilder在此构建后状态标记已变更,不可重新变更,只能获取并使用。构建后的实例,可以注入到HttpSecurity中可以创建Filter的配置器中,自定义安全Filter需要人工或特殊配置来设置。(这块有一定的操作空间)

4、configure()

  • 这个方法和init方法相辅相成,前面已经完成各配置器的初始化,现完成调用各配置器Configurer的configure方法,逐一完成对HttpSecurity的配置。(此块即完成security框架内部Filter的添加,例:LogoutFilter)

5、performBuild()

  • HttpSecurity构建的结果就是SecurityFilterChain对象,此对象的功能就是用来管理security内部Filter集合,创建逻辑也很简单:new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters)。关于SecurityFilterChain的介绍,请点击并移步至此篇。

概要说明:
1、构建流程的入口是我们HttpSecurity.build方法,该方法可由我们主动触发,在WebSecurityConfigurerAdapter适配器模式下,由框架触发
2、授权管理器用来鉴权逻辑,所以一般配置器生产的Filter实例可不用配置,必要的Filter也会做检查。授权管理器对应的构建器在代码里有一定的操作空间,其构建状态影响较大。
3、添加到HttpSecurity的内部安全Filter有很多,这些Filter不影响我们正常使用的ApplicationFilterChain的执行流程,各Filter在集合中的顺序有默认顺序(详见FilterOrderRegistration,包含所有内部过滤器清单及Order),自定义Filter可通过HttpSecurity.addFilterBefore/After等方法指定。

总结:
通过上述两大设计模式对Security框架的解读,文章开篇的几个问题基本都涵盖了,自己可以对照着解读,根据问题和自己的理解去源码中进一步验证,再来一遍debug,基本就可以结束了。下面文章将根据这两篇前缀,直接出一个完整的个人版落地篇,也就是我们实际使用这个框架的内容了。

PS:还有一个重要流程其实没有阐述,就是上一篇的FilterChainProxy与本篇的HttpSecurity构建的SecurityFilterChain对象是如何绑定的,其答案为:WebSecurity通过控制HttpSecurity的配置与构建过程生成SecurityFilterChain,在对应构建方法中使用FilterChainProxy包装,进而发布到外部ApplicationFilterChain过滤器链中。如果感兴趣,以后有机会再开篇详解。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

科技提升安全,基于DETR【DEtection TRansformer】模型开发构建商超扶梯场景下行人安全行为姿态检测识别系统

在商超等人流量较为密集的场景下经常会报道出现一些行人在扶梯上摔倒、受伤等问题&#xff0c;随着AI技术的快速发展与不断普及&#xff0c;越来越多的商超、地铁等场景开始加装专用的安全检测预警系统&#xff0c;核心工作原理即使AI模型与摄像头图像视频流的实时计算&#xf…

使用对象处理流ObjectOutputStream读写文件

注意事项: 1.创建的对象必须实现序列化接口,如果属性也是类&#xff0c;那么对应的类也要序列化 2.读写文件路径问题 3.演示一个例子 &#xff08;1&#xff09;操作的实体类FileModel&#xff0c;实体类中有Map,HashMap这些自带的本身就实现了序列化。 public class File…

运行和部署若依分离版前端

一、运行 一、用vscode打开 二、安装依赖 # 建议不要直接使用 cnpm 安装依赖&#xff0c;会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev浏览器访问 http://localhost:80二、部…

死锁(面试常问)

1.什么是死锁 简单来说就是一个线程加锁后解锁不了 一个线程&#xff0c;一把锁&#xff0c;线程连续加锁两次。如果这个锁是不可重入锁&#xff0c;会死锁。两个线程&#xff0c;两把锁。 举几个例子&#xff0c;1.钥匙锁车里了&#xff0c;车钥匙锁家里了。2. 现在有一本书…

两线制输入馈电型隔离变送器

两线制输入馈电型隔离变送器 产品型号&#xff1a;JSD TA-1021系列 馈电型隔离变送器产品介绍&#xff1a; JSD TA-1021 为两线制输入馈电型高精度隔离变送器&#xff0c;是将输入与输出之间电气绝缘的模拟信号量进行变换、放大、隔离及远传的小型仪表设备&#xff0c;接收仪表…

代码随想录算法训练营Day1 | 704.二分查找、27.移除元素

LeetCode 704 二分查找 题目链接&#xff1a;704.二分查找 本题思路&#xff1a;本题题目写的是二分查找&#xff0c;所以我们用到的算法肯定也是二分查找&#xff0c;需要定义 3个变量。 l: 从数组的下标0开始 r: 数组长度 - 1 mid&#xff1a;&#xff08;l r&#xff09;…

SQL进阶理论篇(二):数据库的设计范式

文章目录 简介数据库的设计范式有哪些数据库中的几种键从1NF到3NF1NF2NF3NFBCNF&#xff08;巴斯范式&#xff09; 反范式设计反范式的适用场景总结参考文献 简介 本小节主要内容&#xff1a; 数据库的设计范式都有哪些数据库的键都有哪些1NF、2NF和3NF都是指什么&#xff1f…

基于Dockerfile创建LNMP

实验组件 172.111.0.10&#xff1a;nginx docker-nginx 172.111.0.20&#xff1a;mysql docker-mysql 172.111.0.30&#xff1a;php docker-php 实验步骤 1.建立nginx-lnmp镜像及容器 cd /opt mkdir nginx cd nginx/ --上传nginx-1.22.0.tar.gz和wordpress-6.4.2-zh_C…

【LeetCode每日一题】1904. 你完成的完整对局数

给你两个字符串 startTime 和 finishTime &#xff0c;均符合 "HH:MM" 格式&#xff0c;分别表示你 进入 和 退出 游戏的确切时间&#xff0c;请计算在整个游戏会话期间&#xff0c;你完成的 完整对局的对局数 。 如果 finishTime 早于 startTime &#xff0c;这表示…

欧拉函数与欧拉定理

文章目录 AcWing 873. 欧拉函数题目链接欧拉函数欧拉函数的证明思路CODE时间复杂度分析 AcWing 874. 筛法求欧拉函数题目链接问题分析与时间复杂度CODE思路 欧拉定理 AcWing 873. 欧拉函数 题目链接 https://www.acwing.com/activity/content/problem/content/942/ 欧拉函数 …

四六级高频词组7

目录 词组 其他文章链接&#xff1a; 词组 251. &#xff08;be&#xff09; equivalent to&#xff08;equal in value&#xff0c; amount&#xff0c; meaning&#xff09; 相等于&#xff0c; 相当于 252. in essence &#xff08;in itsones nature&#xff09; 本质上…

20、备忘录模式(Memento Pattern,不常用)

备忘录模式又叫作快照模式&#xff0c;该模式将当前对象的内部状态保存到备忘录中&#xff0c;以便在需要时能将该对象的状态恢复到原先保存的状态。 备忘录模式提供了一种保存和恢复状态的机制&#xff0c;常用于快照的记录和状态的存储&#xff0c;在系统发生故障或数据发生…

网络安全项目实战(三)--报文检测

6. TCP/IP协议栈及以太网帧 目标 了解TCP/IP协议栈的组织结构掌握以太网帧的数据格式定义能应用编码实现以太网帧的解析方法 6.1. TCP/IP 协议栈 TCP/IP网络协议栈分为应用层&#xff08;Application&#xff09;、传输层&#xff08;Transport&#xff09;、网络层&#xf…

【UML】第4篇 UML公共机制(补扩展机制)

目录 一、扩展机制 1.1 构造型 1.2 标记值&#xff08;Tagged Value&#xff09; 1.3 约束&#xff08;Constraint&#xff09; 上节扩展机制没有讲完&#xff0c;如上图。 一、扩展机制 1.1 构造型 UML中的扩展机制包括约束、构造型和标记值&#xff0c;其中的构造型定义…

yo!这里是Linux信号相关介绍

目录​​​​​​​ 前言 基本介绍 概念 信号列表 信号处理 产生(发送)信号 通过按键产生 系统函数产生 软件条件产生 硬件异常产生 阻塞信号 信号状态 sigset_t 状态相关函数 1.sigprocmask 2.sigpending 捕捉信号 内核态与用户态 捕捉过程 sigaction 后…

分库分表及ShardingShpere-proxy数据分片

为什么需要分库&#xff1f; 随着数据量的急速上升&#xff0c;单个数据库可能会QPS过高导致读写耗时过长而出现性能瓶颈&#xff0c;所以需要考虑拆分数据库&#xff0c;将数据库分布在不同实例上提升数据库可用性。主要的原因有如下&#xff1a; 磁盘存储。业务量剧增&…

nodejs项目设置全局变量(global)

文章目录 前言一、使用global二、解决type typeof globalThis has no index signature.ts问题1、新建 /types/global.d.ts文件2、或者直接在入口文件/src/index.ts定义 三、最终效果鼠标放在global上&#xff0c;可显示global的类型生效了~ ![在这里插入图片描述](https://img-…

I.MX RT1170双核学习(2):双核相互激活和启动流程

RT1170这个芯片带有双核&#xff1a;Cortex-M7和Corterx-M4&#xff0c;两个核都可以独立地运行&#xff0c;当然双核也可以同时运行。在上一篇文章中&#xff0c;介绍了一下在RT1170中消息模块MU的使用&#xff1a;双核通信之MU消息单元详解&#xff0c;因为这是双核之间用来通…

05 python数据容器

5.1 数据容器认识 5.2 python列表 5.2.1 列表的定义 演示数据容器之&#xff1a;list 语法&#xff1a;[元素&#xff0c;元素&#xff0c;....] #定义一个列表List List [itheima,uityu,gsdfg] List1 [itheima,6666,True] print(List) print(List1) print(type(List)) pr…

smartKettle离线部署及问题记录

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 部署&#x1f4d7;源码下载&#x1f4d7;后端部署&#x1f4d5;导入后端项目&#x1f4d5;修改settings.xml(自动下载相关jar包)&#x1f4d5; 编译&#x1f4d5; …