当前位置: 首页 > news >正文

深入理解HttpSecurity的设计

一、HttpSecurity的应用

在前章节的介绍中我们讲解了基于配置文件的使用方式,也就是如下的使用。

也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置文件中定义的http标签。要使用的话方式如下。

通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?我们来详细分析下。

二、HttpSecurity的类图结构

可以看出HttpSecurity的类图结构相对比较简单,继承了一个父类,实现了两个接口。我们分别来看看他们的作用是什么?

1.SecurityBuilder接口

我们先来看看SecurityBuilder接口,通过字面含义我们就可以发现这是一个帮我们创建对象的工具类。

public interface SecurityBuilder<O> { ​ /** * Builds the object and returns it or null. * @return the Object to be built or null if the implementation allows it. * @throws Exception if an error occurred when building the Object */ O build() throws Exception; ​ }

通过源码我们可以看到在SecurityBuilder中给我们提供了一个build()方法。在接口名称处声明了一个泛型,而build()方法返回的正好是这个泛型的对象,其实就很好理解了,也就是SecurityBuilder会创建指定类型的对象。结合HttpSecurity中实现SecurityBuilder接口时指定的泛型我们可以看出创建的具体对象是什么类型。

可以看出SecurityBuilder会通过build方法给我们创建一个DefaultSecurityFilterChain对象。也就是拦截请求的那个默认的过滤器链对象。

然后进入到doBuild()方法,会进入到AbstractConfiguredSecurityBuilder中的方法

@Override protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = BuildState.INITIALIZING; beforeInit(); init(); this.buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); this.buildState = BuildState.BUILDING; // 获取构建的对象,上面的方法可以先忽略 O result = performBuild(); this.buildState = BuildState.BUILT; return result; } }

进入到HttpSecurity中可以查看performBuild()方法的具体实现。

@Override protected DefaultSecurityFilterChain performBuild() { // 对所有的过滤器做排序 this.filters.sort(OrderComparator.INSTANCE); List<Filter> sortedFilters = new ArrayList<>(this.filters.size()); for (Filter filter : this.filters) { sortedFilters.add(((OrderedFilter) filter).filter); } // 然后生成 DefaultSecurityFilterChain return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters); }

在构造方法中绑定了对应的请求匹配器和过滤器集合。

对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析。

2.AbstractConfiguredSecurityBuilder

然后我们再来看看AbstractConfiguredSecurityBuilder这个抽象类,他其实是SecurityBuilder的实现,在这儿需要搞清楚他们的关系。

类型作用
SecurityBuilder声明了build方法
AbstractSecurityBuilder提供了获取对象的方法以及控制一个对象只能build一次
AbstractConfiguredSecurityBuilder除了提供对对象细粒度的控制外还扩展了对configurer的操作

然后对应的三个实现类。

首先 AbstractConfiguredSecurityBuilder 中定义了一个枚举类,将整个构建过程分为 5 种状态,也可 以理解为构建过程生命周期的五个阶段,如下:

private enum BuildState { ​ /** * 还没开始构建 */ UNBUILT(0), ​ /** * 构建中 */ INITIALIZING(1), ​ /** * 配置中 */ CONFIGURING(2), ​ /** * 构建中 */ BUILDING(3), ​ /** * 构建完成 */ BUILT(4); ​ private final int order; ​ BuildState(int order) { this.order = order; } ​ public boolean isInitializing() { return INITIALIZING.order == this.order; } ​ /** * Determines if the state is CONFIGURING or later * @return */ public boolean isConfigured() { return this.order >= CONFIGURING.order; } ​ }

通过这些状态来管理需要构建的对象的不同阶段。

2.1 add方法

AbstractConfiguredSecurityBuilder中方法概览

我们先来看看add方法。

private <C extends SecurityConfigurer<O, B>> void add(C configurer) { Assert.notNull(configurer, "configurer cannot be null"); Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer .getClass(); synchronized (this.configurers) { if (this.buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } List<SecurityConfigurer<O, B>> configs = null; if (this.allowConfigurersOfSameType) { configs = this.configurers.get(clazz); } configs = (configs != null) ? configs : new ArrayList<>(1); configs.add(configurer); this.configurers.put(clazz, configs); if (this.buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } } ​ /** * Gets all the {@link SecurityConfigurer} instances by its class name or an empty * List if not found. Note that object hierarchies are not considered. * @param clazz the {@link SecurityConfigurer} class to look for * @return a list of {@link SecurityConfigurer}s for further customization */ @SuppressWarnings("unchecked") public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) { List<C> configs = (List<C>) this.configurers.get(clazz); if (configs == null) { return new ArrayList<>(); } return new ArrayList<>(configs); }

add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到 configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。

2.2 doBuild方法

然后来看看doBuild方法中的代码

@Override protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = BuildState.INITIALIZING; beforeInit(); //是一个预留方法,没有任何实现 init(); // 就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化 this.buildState = BuildState.CONFIGURING; beforeConfigure(); // 是一个预留方法,没有任何实现 configure(); // 就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。 this.buildState = BuildState.BUILDING; O result = performBuild(); // 是真正的过滤器链构建方法,但是在 AbstractConfiguredSecurityBuilder中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中 this.buildState = BuildState.BUILT; return result; } }

init方法:完成所有相关过滤器的初始化

private void init() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.init((B) this); // 初始化对应的过滤器 } for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) { configurer.init((B) this); } }

configure方法:完成HttpSecurity和对应的过滤器的绑定。

private void configure() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.configure((B) this); } }

3.HttpSecurity

HttpSecurity 做的事情,就是进行各种各样的 xxxConfigurer 配置

HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们就不一一介绍 了。每个配置方法的结尾都会来一句 getOrApply,这个是干嘛的?我们来看下

private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer) throws Exception { C existingConfig = (C) getConfigurer(configurer.getClass()); if (existingConfig != null) { return existingConfig; } return apply(configurer); }

getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就是去查看当前 这个 xxxConfigurer 是否已经配置过了。 如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply 方法最终会调 用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置 configurer 收集起来 HttpSecurity 中还有一个 addFilter 方法.

@Override public HttpSecurity addFilter(Filter filter) { Integer order = this.filterOrders.getOrder(filter.getClass()); if (order == null) { throw new IllegalArgumentException("The Filter class " + filter.getClass().getName() + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); } this.filters.add(new OrderedFilter(filter, order)); return this; }

这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这个方法, (xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。

小结:这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简单的重 复的 xxxConfigurer 配置了

http://www.jsqmd.com/news/970560/

相关文章:

  • Warcraft Helper终极指南:5分钟解决魔兽争霸III所有Win10/Win11兼容性问题
  • 群晖NAS终极升级:Realtek USB以太网驱动完整实战指南
  • 博客园 高性价比滤袋厂家 - 资讯纵览
  • C#桌面程序入门实战:用Access做账号系统,带注册登录界面和完整VS工程
  • 百度网盘提取码智能获取:baidupankey如何实现3秒极速查询
  • Windows端口转发终极指南:3分钟学会图形化配置工具PortProxyGUI
  • 别急着续费!CSDN AI年度套餐隐藏条款曝光:小额体验期可解锁的3个高价值接口权限
  • 微信小程序日历组件:5分钟打造专业级日期管理功能 [特殊字符]
  • 常州母婴除甲醛CMA甲醛检测治理公司深度测评:绿呼吸环保稳居榜首 - 一修哥咨询
  • 【飞机】基于matlab数据驱动的多传感器飞机健康监测系统(漂移检测、RUL预测和风险分析)【含Matlab源码 15600期】
  • 专业级AI动画生成:ComfyUI-AnimateDiff-Evolved完整指南
  • wx_calendar:微信小程序日历组件的架构设计与高效集成方案
  • 2026市北区门窗选购避坑指南:本地五大口碑品牌综合实测与推荐 - GrowthUME
  • 57. django之Cookie和Session
  • 广州老房翻新多少钱?2026年各项目费用明细+避坑指南+公司推荐 - 优家闲谈
  • 传统老年人必须静养,编写程序根据心肺功能,肢体数据,判定老人适宜活动量,破除静养误区。
  • 2026降AIGC软件实测:10款工具对比,论文质量提升秘籍
  • Windows任务栏透明化神器:3分钟让你的桌面焕然一新!
  • 书匠策AI官网www.shujiangce.com:你的期刊论文搭子,比导师还“懂行“
  • 安顺母婴除甲醛CMA甲醛检测治理公司深度测评:绿呼吸环保稳居榜首 - 一修哥咨询
  • 跟着 MDN 学JavaScript day_7:数学运算与逻辑判断实战测试
  • 【飞机】基于matlab固定翼飞机动力学与控制【含Matlab源码 15601期】
  • SteamAutoCrack:终极游戏DRM破解工具使用完全指南
  • 沧州母婴除甲醛CMA甲醛检测治理公司深度测评:绿呼吸环保稳居榜首 - 一修哥咨询
  • 成都装修公司哪家好?2026年主流公司报价对比+怎么选 - 优家闲谈
  • 保姆级教程:手把手教你用Spring Boot集成农行openbank-sdk-java完成H5电子账户开户
  • 8分钟极速上手:用AI智能分层工具彻底告别手动抠图烦恼
  • EdB Prepare Carefully:从随机开局到精准定制的RimWorld角色管理解决方案
  • 别再死磕公式了!用Python实战模拟TDOA定位(从Chan到Fang算法对比)
  • 5分钟快速掌握:NcmpGui免费极速NCM音乐转换终极方案