Spring Security: 整体架构

news2024/11/28 10:40:20

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.coloradmin.cn/o/1146070.html

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

相关文章

C#,数值计算——分类与推理,基座向量机的 Svmgenkernel的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public abstract class Svmgenkernel { public int m { get; set; } public int kcalls { get; set; } public double[,] ker { get; set; } public double[] y { get; set…

构建第二大脑#知识库使用指南

fortelabs.com/blog/basboverview 《构建第二大脑&#xff1a;入门指南》提到一个步骤&#xff1a; 通过策划和管理您的个人信息流来减轻压力和“信息过载”创建一个内心平静的数字环境充分发挥您周围丰富学习资源的价值&#xff0c;例如在线课程、网络研讨会、书籍、文章、论坛…

threejs(8)-详解光线投射与物体交互

详解光线投射与物体交互 import * as THREE from "three"; // 导入轨道控制器 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; // 导入动画库 import gsap from "gsap"; // 导入dat.gui import * as dat from &qu…

QVHZO-A-06/18、QVHZE-A-06/36比例流量控制阀放大器

QVHZO-A-06/36、QVHZO-A-06/12、QVHZO-A-06/45、QVHZO-A-06/18、QVKZOR-A-10/65、QVKZOR-A-10/90、QVHZE-A-06/36、QVHZE-A-06/12、QVHZE-A-06/45、QVHZE-A-06/18、QVKZE-A-10/65、QVKZE-A-10/90带压力补偿的比例流量控制阀&#xff0c;直动式&#xff0c;不带位置传感器&#…

圆锥面积 题解

圆锥体积 描述 已知一个圆锥体的高 h 和底面积的半径 r &#xff0c;求该圆锥体的体积&#xff0c;保留3位小数。 圆锥底部圆面积S的计算公式为&#xfffd;&#xfffd;∗&#xfffd;∗&#xfffd;Sπ∗r∗r 这里的&#xfffd;3.14159π3.14159 圆锥体的体积V计算公式为…

可以提取图像文本的 5 大 Python 库

主要是了解并掌握文本定位和识别的OCR工具哦~ 光学字符识别是一个古老但依然具有挑战性的问题&#xff0c;涉及从非结构化数据中&#xff08;包括图像和PDF文档&#xff09;检测和识别文本。它在银行、电子商务和社交媒体内容管理等领域具有广泛的应用。 但与数据科学中的每个主…

FreeRTOS中osDelay和HAL_Delay的区别

问题场景 在FreeRTOS中创建了线程A、线程B&#xff0c;其中线程A优先级大于线程B。线程A、B任务代码如下: void A(void *argument) {while (1){printf("A\r\n");HAL_Delay(1000);} }void B(void *argument) {while (1){printf("B\r\n");HAL_Delay(1000);} …

基于springboot实现校园疫情防控系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现校园疫情防控系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&am…

回溯法(1)--装载问题和0-1背包

一、回溯法 回溯法采用DFS&#xff0b;剪枝的方式&#xff0c;通过剪枝删掉不满足条件的树&#xff0c;提高本身作为穷举搜索的效率。 回溯法一般有子集树和排列树两种方式&#xff0c;下面的装载问题和01背包问题属于子集树的范畴。 解空间类型&#xff1a; 子集树&#xff1…

【C++项目】高并发内存池第七讲性能分析

目录 1.测试代码2.代码介绍3.运行结结果 1.测试代码 #include"ConcurrentAlloc.h" #include"ObjectPool.h" #include"Common.h" void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds) {std::vector<std::thread> vthread(…

超强Redis基础学习 优化 使用 常见问题

问题大纲 为什么Redis可以这么快&#xff1f; 它是纯内存操作&#xff0c;内存本身就很快 其次&#xff0c;它是单线程的&#xff0c;Redis服务器核心是基于非阻塞的IO多路复用机制&#xff0c;单线程避免了多线程的频繁上下文切换问题 Redis的持久化机制 Redis提供了持久化…

会声会影2024输出文件太大什么原因 会声会影输出文件处于保护状态什么原因

会声会影2024是一款专业的视频编辑软件&#xff0c;它由于简单易学的操作被众人所喜爱。在会声会影中编辑好的视频一般以渲染的形式导出保存&#xff0c;但是有时会出现输出文件太大的情况&#xff0c;这到底是什么原因呢&#xff1f;下面由我带大家一起来了解会声会影输出文件…

git本地搭建服务器[Vmware虚拟机访问window的git服务器]

先按照https://zhuanlan.zhihu.com/p/494988089说明下载好Gitblit然后复制到tomcat的webapps目录下,如下: 双击"startup.bat"启动tomcat: 然后访问"http://127.0.0.1:8080/gitblit/"即可看到git的界面: 说明git服务器已经能够成功运行了! Vmware虚拟机…

机器学习(五)如何理解机器学习三要素

1.8如何理解机器学习三要素 统计学习模型策略算法 模型&#xff1a;规律yaxb 策略&#xff1a;什么样的模型是好的模型&#xff1f;损失函数 算法&#xff1a;如何高效找到最优参数&#xff0c;模型中的参数a和b 1.8.1模型 机器学习中&#xff0c;首先要考虑学习什么样的…

5G 3GPP全球频谱介绍

所谓 “频谱”&#xff0c;是指特定类型的无线通信所在的射频范围。不同的无线技术使用不同的频谱&#xff0c;因此互不干扰。由于一项技术的频谱是有限的&#xff0c;因此频谱空间存在大量竞争&#xff0c;并且人们也在不断开发和增强全新的、高效率的频谱使用方式。 介绍5G …

ConcurrentHashMap 的 size()方法是线程安全的吗?为什么

ConcurrentHashMap 的 size()方法是非线程安全的。也就是说&#xff0c;当有线程调用 put 方法在添加元素的时候&#xff0c;其他线程在调用 size()方法获取的元素个数和实际存储元素个数是不一致的。原因是 size()方法是一个非同步方法&#xff0c;put()方法和 size()方法并没…

获取某个抖音用户的视频列表信息

思路 确定url确定并获取相关参数构造header发送请求解析数据输出数据 运行结果 代码 import requests # 获取某个用户的的视频信息&#xff0c;截至20231028&#xff0c;程序可以正常运行。 # 构造请求头header headers {User-Agent:..........................,Cookie:...…

10分钟了解JWT令牌 (JSON Web)

10分钟了解JSON Web令牌&#xff08;JWT&#xff09; JSON Web Token&#xff08;JWT&#xff09;是目前最流行的跨域身份验证解决方案。今天给大家介绍JWT的原理和用法。 1.跨域身份验证 Internet服务无法与用户身份验证分开。一般过程如下。 1.用户向服务器发送用户名和密码。…

IOC课程整理-12 Spring 国际化

1. Spring 国际化使用场景 2. Spring 国际化接口 3. 层次性 MessageSource 4. Java 国际化标准实现 5. Java 文本格式化 6. MessageSource 开箱即用实现 7. MessageSource 內建依赖 8. 课外资料 9. 面试题精选 Spring 国际化接口有哪些&#xff1f; • 核心接口 - MessageSour…

Linux操作系统 - 从概念上认识进程

目录 前置知识 冯诺依曼和现代计算机结构 操作系统的理解 进程的概念 进程和PCB 查看进程信息的指令 - ps 进程的一些特性 进程标识符 - PID 进程状态 进程状态的概念 Linux下的进程状态 父子进程 子进程的创建 - fork 僵尸进程 孤儿进程 进程切换 CPU上下文切…