Spring Security: 整体架构
Filter
Spring Security 是基于 Sevlet Filter 实现的。下面是一次 Http 请求从 client 出发,与 Servlet 交互的图:
当客户端发送一个请求到应用,容器会创建一个 FilterChain
,FilterChain
中包含多个 Filter
和 Servlet
。这些 Filter
和 Servlet
是用来处理 HttpServletRequest
的。在 Spring MVC 应用中,Servlet
实际上是一个 DispatcherServlet
。
DelegatingFilterProxy
Spring 提供了一个名字叫 DelegatingFilterProxy
的 Filter
实现。直译过来就是委托过滤器代理。DelegatingFilterProxy
可以对 Servlet
容器的生命周期和 Spring 的 ApplicationContext
容器进行桥接。Servlet
容器可以通过非 Spring 的方式注册 Filter
实例。也就是说我们可以通过 Servlet
容器机制注册 DelegatingFilterProxy
,但是却可以把所有的工作委托给实现了 Filter
的 Spring Bean。
DelegatingFilterProxy
会从 ApplicationContext
容器中寻找并调用 Bean Filter0
。DelegatingFilterProxy
允许推迟查找 Filter
Bean 实例。这很重要,因为容器在启动前需要注册 Filter
实例。
FilterChainProxy
FilterChainProxy
由 DelegatingFilterProxy
包裹的一个 Filter 。FilterChainProxy
是一个通过 SecurityFilterChain
来包含多个 Filter
实例的特殊的 Filter
。
SecurityFilterChain
SecurityFilterChain
被 FilterChainProxy
使用。SecurityFilterChain
用来确定当前请求应该调用哪些 Filter
实例。
在 SecurityFilterChain
中的 Security Filters
是典型的 Bean。但是它们是随着 FilterChainProxy
一起注册的,而不是跟着 DelegatingFilterProxy
一起注册的。FilterChainProxy
是 Spring Security 支持的起点。所以在排除认证会权限的问题时,可以将断点打在 FilterChainProxy
里。 下图展示多个 SecurityFilterChain
实例:
在多 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 的顺序如下:
Filter | Added by |
---|---|
CsrfFilter | HttpSecurity#csrf |
UsernamePasswordAuthenticationFilter | HttpSecurity#formLogin |
BasicAuthenticationFilter | HttpSecurity#httpBasic |
AuthorizationFilter | HttpSecurity#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 {@Overridepublic 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
中,它可以将 AccessDeniedException
和 AuthenticationException
两个异常转换成 http response。
下图是 ExceptionTranslationFilter
与其他组件的关系:
如果应用没有抛出 AccessDeniedException
和 AuthenticationException
这两个异常,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();
}
相关文章:

Spring Security: 整体架构
Filter Spring Security 是基于 Sevlet Filter 实现的。下面是一次 Http 请求从 client 出发,与 Servlet 交互的图: 当客户端发送一个请求到应用,容器会创建一个 FilterChain,FilterChain 中包含多个 Filter 和 Servlet。这些 Fi…...

JavaScript进阶知识汇总~
JavaScript 进阶 给大家推荐一个实用面试题库 1、前端面试题库 (面试必备) 推荐:★★★★★ 地址:web前端面试题库 1.原型链入门 1) 构造函数 当我们自定义一个函数时(箭头函数与生成器函数除外),这个函…...

理解C#中对象的浅拷贝和深拷贝
本文章主要介绍C#中对象的拷贝,其中包括浅拷贝和深拷贝,以及浅拷贝和深拷贝的实现方式,不同的实现方式之间的性能对比。 1、浅拷贝和深拷贝 浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它…...

js 生成随机数(含随机颜色)
生成 0-1 之间的随机数 Math.random()生成 0-x 之间的随机整数: Math.round(Math.random()*x)生成 min-max 之间的随机整数: Math.round(Math.random()*(max-min)min)生成N位随机数 /*** 函数--生成N位随机数* param {*} N 数字的长度*/ function random…...

【axios】axios的基本使用
一、 Axios简介 1、 Axios是什么? Axios是一个基于promise的HTTP库,类似于jQuery的ajax,用于http请求。可以应用于浏览器端和node.js,既可以用于客户端,也可以用于node.js编写的服务端。 2.、Axios特性 支持Promis…...

React 在非组件环境切换路由
我的react-router-dom版本是6.16.0。之前在react中是这样配置路由的 App.jsx import ReactDOM from react-dom/client; import { HashRouter, Route, Routes } from react-router-dom;const root ReactDOM.createRoot(document.getElementById("app")); root.rend…...

Oracle高速批量速插入数据解决方案
最近做短信群发项目有一个需求,需要客户大批量(十万级)导入数据. 开始是用insert单条数据,10万条数据要20分钟 后来发现可以用insert all 一条sql一次导入500条记录,这样10万条数据只用了1.5分钟,导入速度提高了近来20倍 下面就使用insert all的心得体会记录如下. 使用方法: i…...

基于单片机嵌入式的智能交通信号灯管理系统的设计与实现
项目介绍 有目共睹电子设备已经席卷了整个人类生活,他们不断改善着人们的起居住行,这也就促进了嵌入式人工智能的快速发展。 本课设模拟系统分为软硬件两部分组成。硬件部分是由两位8段数码管和LED灯构成的显示系统和控制电路等组成,能较好的…...

在全新ubuntu上用gpu训练paddleocr模型遇到的坑与解决办法
目录 一. 我的ubuntu版本二.首先拉取paddleocr源代码三.下载模型四.训练前的准备1.在源代码文件夹里创造一个自己放东西的文件2.准备数据2.1数据标注2.2数据划分 3.改写yml配置文件4.…...

React之服务端渲染
一、是什么 在SSR中 (opens new window),我们了解到Server-Side Rendering ,简称SSR,意为服务端渲染 指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可…...

jetson nano刷机更新Jetpack
只是记录个人在使用英伟达jetson Nano的经历,由于头一次尝试,所以特此记录需要的问题和经验。 一,英伟达刷机教程(jetson nano 版本) 本次我是直接刷机到TF卡,然后TF卡作为启动盘进行启动,我看网上有带EMMC版本的,好像可以直接把系统镜像安装到EMMC里面。但是有个问题…...
Android官方ShapeableImageView描边/圆形/圆角图,xml布局实现
Android官方ShapeableImageView描边/圆形/圆角图,xml布局实现 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.…...

ubuntu扩大运行内存, 防止编译卡死
首先查看交换分区大小 grep SwapTotal /proc/meminfo 1、关闭交换空间 sudo swapoff -a 2、扩充交换空间大小,count64就是64G 1G x 64 sudo dd if/dev/zero of/swapfile bs1G count64 3、设置权限 sudo chmod 600 /swapfile 4、指定交换空间对应的设备文件 …...

Kafka集群修改单个Topic数据保存周期
在大数据部门经常使用Kafka集群,有的时候大数据部门可能在Kafka中的Topic数据保存时间不需要很长,一旦被消费后就不需要一直保留。默认Topic存储时间为7day,个别的Topic或者某台Kafka集群需要修改Topic数据保存的一个周期,调整为3…...

selenium模拟登录无反应
在使用自动化工具selenium时对某些网站登录界面send_keys输入账号密码,运行时却没有自己想要的结果出现,这是因为你碰到前端二般的开发人员,他们用的是HTML嵌套,这对后端人员造成了一些麻烦,废话不多说,直接…...

指针变量未分配空间或者初始化为空指针使用问题
提示:关于指针 文章目录 前言一、指针的使用总结 前言 在看c书籍的时候,看到浅复制和深复制时,说到成员为指针的时候,会出异常。但是其实没有更多的感想,但是联想到上次考试指针没分配空间导致程序异常的情况…...

力扣第763题 划分字母区间 c++ 哈希 + 双指针 + 小小贪心
题目 763. 划分字母区间 中等 相关标签 贪心 哈希表 双指针 字符串 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 注意,划分结果需要满足:将所有划分结果按顺序连接,得…...

js 代码中的 “use strict“; 是什么意思 ?
use strict 是一种 ECMAscript5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行。 设立"严格模式"的目的,主要有以下几个: 消除 Javascript 语法的一些不合理、不严谨之处,…...

用于读取验证码的 OCR 模型
介绍 此示例演示了使用功能 API 构建的简单 OCR 模型。除了结合 CNN 和 RNN 之外,它还说明了如何实例化新层并将其用作“端点层”来实现 CTC 损失。 设置 import os import numpy as np import matplotlib.pyplot as pltfrom pathlib import Path from collections import Co…...

Uniapp 跳转回上一页面并刷新页面数据
比如我从A页面跳转到B页面 然后再从B页面返回到A页面 顺带刷新一下A页面数据 let pages getCurrentPages(); // 当前页面 //获取当前页面栈let beforePage pages[pages.length - 3]; // //获取上一个页面实例对象beforePage.$vm.reloadList(); //调用它方法然后跳转…...

DeOldify 接口化改造 集成 Flask
类似的图片修复项目 GFPGAN 的改造见我另一篇文 https://blog.csdn.net/weixin_43074462/article/details/132497146 DeOldify 是一款开源软件,用于给黑白照片或视频上色,效果还不错。 安装部署教程请参考别的文章,本文基于你给项目跑通&…...

Vue 3响应式对象: ref和reactive
目录 什么是响应式对象? Ref Reactive Ref vs Reactive 适用场景: 访问方式: 引用传递: 性能开销: 响应式对象优点 响应式对象缺点 总结 Vue 3作为一种流行的JavaScript框架,提供了响应式编程的…...

Unity3D 如何用unity引擎然后用c#语言搭建自己的服务器
Unity3D是一款强大的游戏开发引擎,可以用于创建各种类型的游戏。在游戏开发过程中,经常需要与服务器进行通信来实现一些功能,比如保存和加载游戏数据、实现多人游戏等。本文将介绍如何使用Unity引擎和C#语言搭建自己的服务器,并给…...

带有 Vagrant 和 Virtualbox 的 Elasticsearch 集群
模拟分布式存储和计算环境的一种简单方法是使用 Virtualbox 作为 VM(“虚拟机”)的提供者,使用 Vagrant 作为前端脚本引擎来配置、启动和停止这些 VM。这篇文章的目标是构建一个集群虚拟设备,提供 Elasticsearch 作为可由主机使用…...

Cross Site Scripting (XSS)
攻击者会给网站发送可疑的脚本,可以获取浏览器保存的网站cookie, session tokens, 或者其他敏感的信息,甚至可以重写HTML页面的内容。 背景 XSS漏洞有不同类型,最开始发现的是存储型XSS和反射型XSS,2005,Am…...

VDA到Excel方案介绍之自定义邮件接收主题
VDA标准是德国汽车工业协会(Verband der Automobilindustrie,简称VDA)制定的一系列汽车行业标准。这些标准包括了汽车生产、质量管理、供应链管理、环境保护、安全性能等方面的规范和指南。VDA标准通常被德国和国际上的汽车制造商采用&#x…...

【opencv】【CPU】windows10下opencv4.8.0-cuda C++版本源码编译教程
【opencv】【CPU】windows10下opencv4.8.0-cuda C版本源码编译教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【opencv】【CPU】windows10下opencv4.8.0-cuda C版本源码编译教程前言准备工具cmakeopencv4.8.0opencv_contrib CMake编译VS2…...

多分类loss学习记录
这里简单的记录在人脸识别/声纹识别中常用的分类loss。详细原理可以参考其他博客。 扩展资料1 扩展资料2 L-softmax A-softmax AM-softmax L-softmax :基于softmax加入了margin, Wx 改写为||w||||x||cos(角度),将角度变为了m角度 A-softmax &…...

Linux创建逻辑卷并扩容(超详细)
目录 编辑 一、概念解析 1、LV逻辑卷 2、PV物理卷 3、VG卷组 二、扩容前准备 三、创建逻辑卷并扩容 1、打开虚拟机 2、进入root用户 3、查看新加入的硬盘 4、创建主分区 5、创建物理卷 6、打包为一个卷组 7、创建逻辑卷 8、格式化逻辑卷 9、挂载逻辑卷--开机自…...

buuctf_练[安洵杯 2019]easy_web
[安洵杯 2019]easy_web 文章目录 [安洵杯 2019]easy_web掌握知识解题思路代码分析正式解题 关键paylaod 掌握知识 url地址和源代码的信息捕捉;图片和base64之间转换;base64和十六进制编码的了解;代码审计,绕过正则匹配对关键字的…...