Spring Security: 整体架构

Filter

Spring Security 是基于 Sevlet Filter 实现的。下面是一次 Http 请求从 client 出发,与 Servlet 交互的图:

image.png

当客户端发送一个请求到应用,容器会创建一个 FilterChainFilterChain 中包含多个 FilterServlet。这些 FilterServlet 是用来处理 HttpServletRequest 的。在 Spring MVC 应用中,Servlet 实际上是一个 DispatcherServlet

DelegatingFilterProxy

Spring 提供了一个名字叫 DelegatingFilterProxyFilter 实现。直译过来就是委托过滤器代理DelegatingFilterProxy 可以对 Servlet 容器的生命周期和 Spring 的 ApplicationContext 容器进行桥接。Servlet 容器可以通过非 Spring 的方式注册 Filter 实例。也就是说我们可以通过 Servlet 容器机制注册 DelegatingFilterProxy,但是却可以把所有的工作委托给实现了 Filter 的 Spring Bean。

image.png

DelegatingFilterProxy 会从 ApplicationContext 容器中寻找并调用 Bean Filter0DelegatingFilterProxy 允许推迟查找 Filter Bean 实例。这很重要,因为容器在启动前需要注册 Filter 实例。

FilterChainProxy

FilterChainProxyDelegatingFilterProxy 包裹的一个 Filter 。FilterChainProxy 是一个通过 SecurityFilterChain 来包含多个 Filter 实例的特殊的 Filter

image.png

SecurityFilterChain

SecurityFilterChainFilterChainProxy 使用。SecurityFilterChain 用来确定当前请求应该调用哪些 Filter 实例。

image.png

SecurityFilterChain 中的 Security Filters 是典型的 Bean。但是它们是随着 FilterChainProxy 一起注册的,而不是跟着 DelegatingFilterProxy 一起注册的。FilterChainProxy 是 Spring Security 支持的起点。所以在排除认证会权限的问题时,可以将断点打在 FilterChainProxy 里。 下图展示多个 SecurityFilterChain 实例:

image.png

在多 SecurityFilterChain 实例配置中,FilterChainProxy 决定哪个 SecurityFilterChain 将会被使用。只有第一个被匹配上的 SecurityFilterChain 才会被调用。比如一个请求 /api/a,它能与第0个 SecurityFilterChain /api/**和n个 /** 匹配,但是只会调用第0个。

每个 SecurityFilterChain 都有自己独立且隔离的 Filter 实例。

Security Filters

Security Filters 被通过 SecurityFilterChain 的 API 插入到 FilterChainProxy 中。这些 filter 可以被用作不同的目的。比如:认证、鉴权、漏洞保护等。这些 filter 以特定的顺序进行执行,从而保证它们在正确的时机被调用,比如认证需要在鉴权之前被执行。通常我们不需要关心这些 filter 的顺序,但是我们可以在 FilterOrderRegistration 类中查看这些顺序。

@Configuration  
@EnableWebSecurity  
public class SecurityConfig {  
  
@Bean  
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {  
    http.csrf(Customizer.withDefaults())  
        .authorizeHttpRequests(authorize -> authorize  
        .anyRequest().authenticated())  
    .httpBasic(Customizer.withDefaults())  
    .formLogin(Customizer.withDefaults());  
    return http.build();  
    }  
}

上面的代码会导致 filter 的顺序如下:

FilterAdded by
CsrfFilterHttpSecurity#csrf
UsernamePasswordAuthenticationFilterHttpSecurity#formLogin
BasicAuthenticationFilterHttpSecurity#httpBasic
AuthorizationFilterHttpSecurity#authorizeHttpRequests

Printing the Security Filters

spring boot 在 info 级别的日志会打印请求要经过哪些 Spring Security 的 filter。在控制台中会打印成一行。与下面的日志看起来会有点不一样:

2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

如果我们想看到 filter 的调用链,以及 Spring Security 的详细报错信息,我们可以通过对日志级别的调整进行打印,将 org.springframework.security 的日志级别调整为:TRACE 即可。

logging:  
  level:  
    root: info  
    org.springframework.security: trace
发出一个 /hello 请求后,控制台输出如下:
2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /hello
2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/15)
2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/15)
2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/15)
2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/15)
2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/15)
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/hello
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl   : Responding with 403 status code
2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

添加自定义的 filter 到 Filter Chain

如果需要添加自定义的 filter,例如:添加一个校验 tenantId 的 filter:

首先定义一个 filter:

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String tenantId = request.getHeader("X-Tenant-Id"); 
        boolean hasAccess = isUserAllowed(tenantId); 
        if (hasAccess) {
            filterChain.doFilter(request, response); 
            return;
        }
        throw new AccessDeniedException("Access denied"); 
    }
}

然后将自定义的 filter 加入到 security filter chain 中:

@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
    http // ... 
    .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); 
    return http.build(); 
}

添加 filter 时,可以指定 filter 位于某个给定的 filter 的前或者后。从而保证 filter 的执行顺序。

需要注意的是,在定义 Filter 时,不要将 filter 定义为 Spring Bean。因为 Spring 会自动把它注册到 Spring 的容器中,从而导致 filter 被调用两次,一次被 Spring 容器调用,一次被 Spring Security 调用。

如果非要定义为 Spring Bean,可以使用 FilterRegistrationBean 来注册该filter,并把 enabled 属性设置为 false。示例代码如下:

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}

处理 Security Exceptions

ExceptionTranslationFilter 被作为一个 security filter 被插入到 FilterChainProxy 中,它可以将 AccessDeniedExceptionAuthenticationException 两个异常转换成 http response。

下图是 ExceptionTranslationFilter 与其他组件的关系:

image.png

如果应用没有抛出 AccessDeniedExceptionAuthenticationException 这两个异常,ExceptionTranslationFilter 什么都不会做。

在 Authentication 之间保存 Requests

在一个没有认证成功的请求过来后,有必要将 request 缓存起来,为认证成功后重新请求使用。

RequestCacheAwareFilter 类用 RequestCache 来保存 HttpServletRequest。 默认情况下是使用 HttpSessionRequestCache ,下面的代码展示了如何自定义 RequestCache 实现,该实现用于检查 HttpSession 是否存在已保存的请求(如果存在名为 Continue 的参数)。

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}

如果你想禁用 Request 缓存的话,可以使用 NullRequestCache 实现。示例代码如下:

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

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

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

相关文章

代码训练营第53天:动态规划part12|leetcode309买卖股票的最佳时期含冷静期|leetcode714买卖股票的最佳时机含手续费

leetcode309&#xff1a;买卖股票的最佳时机含冷冻期 文章讲解&#xff1a;leletcode309 leetcode714&#xff1a;买卖股票的最佳时机含手续费 文章讲解&#xff1a;leetcode714 目录 1&#xff0c;leetcode309 买卖股票的最佳时机含冷冻期 2&#xff0c;leetcode714 买卖股票…

荣耀推送服务消息分类标准

前言 为了提升终端用户的推送体验、营造良好可持续的通知生态&#xff0c;荣耀推送服务将对推送消息进行分类管理。 消息分类 定义 荣耀推送服务将根据应用类型、消息内容和消息发送场景&#xff0c;将推送消息分成服务通讯和资讯营销两大类别。 服务通讯类&#xff0c;包…

Linux学习第26天:异步通知驱动开发: 主动

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在正式开启今天的学习前&#xff0c;讲一讲为什么标题中加入了【主动】俩字。之前学习的阻塞和非阻塞IO&#xff0c;都是在被动的接受应用程序的操作。而今天的学…

深入浅出排序算法之计数排序

目录 1. 原理 2. 代码实现 3. 性能分析 1. 原理 首先看一个题目&#xff0c;有n个数&#xff0c;取值范围是 0~n&#xff0c;写出一个排序算法&#xff0c;要求时间复杂度和空间复杂度都是O(n)的。 为了达到这种效果&#xff0c;这一篇将会介绍一种不基于比较的排序方法。这…

python,pandas ,openpyxl提取excel特定数据,合并单元格合并列,设置表格格式,设置字体颜色,

python&#xff0c;pandas &#xff0c;openpyxl提取excel特定数据&#xff0c;合并单元格合并列&#xff0c;设置表格格式&#xff0c;设置字体颜色&#xff0c; 代码 import osimport numpy import pandas as pd import openpyxl from openpyxl.styles import Font from op…

【鸿蒙软件开发】ArkTS基础组件之TextClock(时间显示文本)、TextPicker(滑动选择文本)

文章目录 前言一、TextClock1.1 子组件1.2 接口参数TextClockController 1.3 属性1.4 事件1.5 示例代码 二、TextPicker2.1 子组件2.2 接口参数 2.3 属性2.4 事件2.5 示例代码 总结 前言 TextClock组件:通过文本将当前系统时间显示在设备上。支持不同时区的时间显示&#xff0…

uni-app中tab选项卡的实现效果 @click=“clickTab(‘sell‘)“事件可传参数

一、效果图 二、代码 <template><view><view class"choose-tab"><view class"choose-tab-item" :class"chooseTab 0 ? active : " data-choose"0" click"clickTab">选项1</view><view …

docker部署prometheus+grafana服务器监控(三) - 配置grafana

查看 prometheus 访问 http://ip:9090/targets&#xff0c;效果如下&#xff0c;上面我们通过 node_exporter 收集的节点状态是 up 状态。 配置 Grafana 访问 http://ip:3000&#xff0c;登录 Grafana&#xff0c;默认的账号密码是 admin:admin&#xff0c;首次登录需要修改…

【C++初阶(三)】引用内联函数auto关键字

目录 前言 1. 引用 1.1 引用的概念 1.2 引用的特性 1.3 引用的权限 1.4 引用的使用 1.5 引用与指针的区别 2. 内联函数 2.1 什么是内联函数 2.2 内联函数的特性 3. auto关键字 3.1 auto简介 3.2 auto使用规则 3.3 auto不能使用的场景 4. 基于范围的for循环 4.1 范围for…

mathtype怎么更改编号 mathtype章节编号错乱怎么办

mathtype作为一款功能强大的公式编辑器&#xff0c;使用范围广泛&#xff0c;与多款软件兼容。但新手可能会对mathtype的操作不熟悉&#xff0c;不知道如何在mathtype中更改编号&#xff0c;以及解决章节编号错乱问题。本文将围绕mathtype怎么更改编号&#xff0c;mathtype章节…

toon boom harmony基础

以下都是tbh快捷键使用&#xff0c;或者一些常用功能介绍 1、在节点视图中&#xff0c;按回车可直接弹出节点库搜索框 2、中心线编辑器 只能编辑用笔刷画出来的线条&#xff0c;铅笔画出来的线条无法编辑。 3、镜头标记 1 右键箭头方向&#xff0c;可弹出下拉&#xff0c;&am…

StripedFly恶意软件框架感染了100万台Windows和Linux主机

导语 近日&#xff0c;一款名为StripedFly的恶意软件框架在网络安全研究人员的监视之外悄然感染了超过100万台Windows和Linux系统。这款跨平台的恶意软件平台在过去的五年中一直未被察觉。在去年&#xff0c;卡巴斯基实验室发现了这个恶意框架的真实本质&#xff0c;并发现其活…

【C++的OpenCV】第十四课-OpenCV基础强化(二):访问单通道Mat中的值

&#x1f389;&#x1f389;&#x1f389; 欢迎各位来到小白 p i a o 的学习空间&#xff01; \color{red}{欢迎各位来到小白piao的学习空间&#xff01;} 欢迎各位来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; &#x1f496;&#x1f496;&…

现代挖掘机vr在线互动展示厅是实现业务增长的加速度

VR数字博物馆全景展示充分应用5G、VR全景、web3d开发和三维动画等技术&#xff0c;将实体博物馆整体还原到3D数字空间&#xff0c;让游客360全景漫游式参观&#xff0c;无论大小、贵重、破损的典藏展品都能通过3D建模技术&#xff0c;逼真重现到三维虚拟场景中&#xff0c;让参…

在全新ubuntu上用gpu训练paddleocr模型遇到的坑与解决办法

目录 一. 我的ubuntu版本![在这里插入图片描述](https://img-blog.csdnimg.cn/297945917309494ab03b50764e6fb775.png)二.首先拉取paddleocr源代码三.下载模型四.训练前的准备1.在源代码文件夹里创造一个自己放东西的文件2.准备数据2.1数据标注2.2数据划分 3.改写yml配置文件4.…

OS的Alarm定时器调度机制

调度表触发的任务在编译时就被静态定义&#xff0c;任务的触发时间和执行顺序是固定的。这种方式适用于已知的、固定的任务触发模式&#xff0c;例如周期性任务或事件驱动任务。而使用 Alarm 机制触发的任务具有更大的灵活性。Alarm 允许在运行时动态地设置和修改任务的触发时间…

Megatron-LM GPT 源码分析(三) Pipeline Parallel分析

引言 本文接着上一篇【Megatron-LM GPT 源码分析&#xff08;二&#xff09; Sequence Parallel分析】&#xff0c;基于开源代码 GitHub - NVIDIA/Megatron-LM: Ongoing research training transformer models at scale &#xff0c;通过GPT的模型运行示例&#xff0c;从三个维…

论文阅读——GPT3

来自论文&#xff1a;Language Models are Few-Shot Learners Arxiv&#xff1a;https://arxiv.org/abs/2005.14165v2 记录下一些概念等。&#xff0c;没有太多细节。 预训练LM尽管任务无关&#xff0c;但是要达到好的效果仍然需要在特定数据集或任务上微调。因此需要消除这个…

基于边缘智能网关的储能系统安全监测管理方案

“储能系统充电”是配套新能源汽车产业发展的重要应用之一。得益于电池技术的发展&#xff0c;新能源汽车正逐步迈入快充时代&#xff0c;由于在使用快速充电桩时&#xff0c;可能导致用电峰值负荷超过电网的承载能力&#xff0c;对于电网的稳定性和持续性会有较大影响&#xf…

nodejs+vue+elementui+express酒店管理系统

登录&#xff1a;运行系统后&#xff0c;进行登录&#xff0c;可使用本系统。 客房预定&#xff1a;此界面先通过条件查询客房信息&#xff0c;然后进行客房预定。对预定的客房还可以取消和支付操作。 信息查询&#xff1a;可查询所有的公告信息&#xff0c;点击公告名称&#…
最新文章