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

2023-12-14 19:20

本文主要是介绍【个人版】SpringBoot下Spring-Security核心概念解读【一】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SpringBoot + Spring-Security + FilterChainProxy

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

背景: 凡项目内存在与外部系统交互,基本安全校验少不了。因项目规模与框架发展原因,历史项目中很多时候直接在Filter中完成安全校验(HandlerInterceptor同理),基本需求完全够用。现在因微服务、中心化等原因,自己写权限管理逻辑代码量较多,后期修改工作量可能较大,所以spring全家桶中的安全框架spring-security随SpringBoot发展而热门起来。最近走读源码,发现spring-security其实还是以Filter为基础,只是把周边功能及场景通过逻辑封装,进而成为开箱即用的安全框架。

Spring-Securiy的核心类是FilterChainProxy,FilterChainProxy的顶层接口是Filter

所以只要理解了FilterChainProxy类的架构设计、构建流程、执行细节,那么这个框架基本就了解了。关于Filter的深入理解,可以看之前解读:

Filter系列解读:
1、SpringBoot下Filter自动适配
2、SpringBoot下Filter注册流程
3、Filter链式执行设计解读

下面直接上FilterChainProxy二改版源码:

public class org.springframework.security.web.FilterChainProxy extends GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean{// 鉴权过滤器链,根据请求匹配,一般一个(匹配/*)就足够,除非网关类项目且鉴权逻辑差异较大private List<SecurityFilterChain> filterChains;// 请求包装类,授权框架处理定制版private HttpFirewall firewall;// SecurityFilterChain就是spring-security封装的权限处理类,其内部核心就是鉴权Filter// SecurityFilterChain的定义、生成流程及逻辑后续篇说明public FilterChainProxy(List<SecurityFilterChain> filterChains) {this.firewall = new StrictHttpFirewall();this.filterChains = filterChains;}// FilterChainProxy本身就是Filter,且url pattern为/*,所有请求都会在此进入执行public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {this.doFilterInternal(request, response, chain);} catch (Exception var11) {// 异常处理,此处忽略} finally {SecurityContextHolder.clearContext();request.removeAttribute(FILTER_APPLIED);}}private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 请求响应预包装,可不关注FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);// 这里根据请求URI匹配对应SecurityFilterChain,进而获取其包装的【鉴权相关】Filter集合List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);if (filters != null && filters.size() != 0) {// 将参数chain和filters用内部类封装成新的FilterChain,使执行流程完全等同于普通过滤器VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);// 内部定义FilterChain执行入口virtualFilterChain.doFilter(firewallRequest, firewallResponse);} else {firewallRequest.reset();// 无权限处理相关类,执行外部过滤器链的下一个,直接忽略FilterChainProxy过滤器chain.doFilter(firewallRequest, firewallResponse);}}// 通过请求获取鉴权过滤器列表,逻辑较为简单private List<Filter> getFilters(HttpServletRequest request) {int count = 0;Iterator var3 = this.filterChains.iterator();SecurityFilterChain chain;do {if (!var3.hasNext()) {return null;}chain = (SecurityFilterChain)var3.next();} while(!chain.matches(request));// 根据匹配到的SecurityFilterChain,返回其包含的所有Filterreturn chain.getFilters();}//VirtualFilterChain可类比ApplicationFilterChain,此设计极高解耦代码执行流程private static final class VirtualFilterChain implements FilterChain {// 保存原始chain对象,执行完所有授权Filter后,继续执行原始链的下一个过滤器private final FilterChain originalChain;// 授权过滤器集合private final List<Filter> additionalFilters;private final FirewalledRequest firewalledRequest;// 鉴权过滤器总数private final int size;// 当前过滤器执行位置索引private int currentPosition;private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain, List<Filter> additionalFilters) {this.currentPosition = 0;this.originalChain = chain;this.additionalFilters = additionalFilters;this.size = additionalFilters.size();this.firewalledRequest = firewalledRequest;}// 此块逻辑完美体现数组 + Filter类设计的美感public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (this.currentPosition == this.size) {// 授权过滤器执行完成this.firewalledRequest.reset();this.originalChain.doFilter(request, response);} else {// 循环做权限上下文中的所有检查及鉴权操作,注意索引自增Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition++);nextFilter.doFilter(request, response, this);}}}
}

FilterChainProxy的核心代码及相关介绍已在上面具体标注了,框架帮我们把业务无关的代码全部封装起来,我们只需要关注核心的鉴权管理,剩下的全交给框架了。

除了FilterChainProxy源码解读,还有几个问题需要弄明白:
1、FilterChainProxy作为过滤器,是怎么注册到Spring容器的?和普通过滤器注册是否不同?
2、FilterChainProxy作为过滤器,有没有优先级要求?
3、我们自定义的鉴权类过滤器,是否会影响ApplicationFilterChain原始链的执行?
4、security框架,为我们封装了哪些内部过滤器?
5、我们定义的普通过滤器,会自动添加到授权过滤器列表吗?

FilterChainProxy注册:

@Configuration(proxyBeanMethods = false
)
public class org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {@Bean(name = {"springSecurityFilterChain"})public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();boolean hasFilterChain = !this.securityFilterChains.isEmpty();Iterator var7 = this.securityFilterChains.iterator();while(true) {while(var7.hasNext()) {SecurityFilterChain securityFilterChain = (SecurityFilterChain)var7.next();this.webSecurity.addSecurityFilterChainBuilder(() -> {return securityFilterChain;});Iterator var5 = securityFilterChain.getFilters().iterator();while(var5.hasNext()) {Filter filter = (Filter)var5.next();if (filter instanceof FilterSecurityInterceptor) {this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter);break;}}}var7 = this.webSecurityCustomizers.iterator();while(var7.hasNext()) {WebSecurityCustomizer customizer = (WebSecurityCustomizer)var7.next();customizer.customize(this.webSecurity);}return (Filter)this.webSecurity.build();}}
}@Autowired(required = false)void setFilterChains(List<SecurityFilterChain> securityFilterChains) {this.securityFilterChains = securityFilterChains;}
protected Filter performBuild() throws Exception {int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList();var4 = this.securityFilterChainBuilders.iterator();while(var4.hasNext()) {SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var4.next();SecurityFilterChain securityFilterChain = (SecurityFilterChain)securityFilterChainBuilder.build();securityFilterChains.add(securityFilterChain);requestMatcherPrivilegeEvaluatorsEntries.add(this.getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));}FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);Filter result = filterChainProxy;this.postBuildAction.run();return (Filter)result;
}

重点:
1、securityFilterChains由外部实例化后,注入到该配置类中【列表注入】
2、SecurityFilterChain由HttpSecurity类构建而来,WebSecurity构建结果为FilterChainProxy
3、FilterSecurityInterceptor负责安全检查,未授权请求在此拒绝
4、FilterSecurityInterceptor在SecurityFilterChain的Filter列表中最后一个

我们知道过滤器真正执行是否两块逻辑组成:Filter实例 + 匹配|执行元数据,所以在过去spring时代我们通常需要FilterRegistrationBean完成注册。元数据关系到过滤器执行的时机及顺序,这块在哪里配置的呢?

@ConfigurationProperties(prefix = "spring.security"
)
public class SecurityProperties {public static final int BASIC_AUTH_ORDER = 2147483642;public static final int IGNORED_ORDER = Integer.MIN_VALUE;public static final int DEFAULT_FILTER_ORDER = -100;private final Filter filter = new Filter();private final User user = new User();public static class Filter {private int order = -100;private Set<DispatcherType> dispatcherTypes;public Filter() {this.dispatcherTypes = new HashSet(Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));}}
}
@EnableConfigurationProperties({SecurityProperties.class})
public class org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration {@Bean@ConditionalOnBean(name = {"springSecurityFilterChain"})public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);registration.setOrder(securityProperties.getFilter().getOrder());registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));return registration;}
}

说明:
1、配置文件中,以spring.security前缀可配置security过滤器顺序
2、过滤器默认优先级为-100,我们自定义过滤器不要设置负数,负数留给其他框架提前预处理
3、Filter封装类不是FilterRegistrationBean,DelegatingFilterProxyRegistrationBean延迟了FilterChainProxy的实例化,为自定义配置执行留下较大的配置事件
4、通过RegistrationBean,我们在处理请求时,就可以在原始ApplicationFilterChain链的靠前位置执行鉴权逻辑了
5、一般我们在Filter获取Request对象的实际类型时,基本都是RequestFacade类型,但当使用security框架后,我们接收到的可能是其他请求Wrapper类型,这时候如果自定义Filter存在Request类型的反射处理,就会报错了。

以下为security框架自带的部分Filter清单:

	DisableEncodeUrlFilterWebAsyncManagerlntegrationFilterSecurityContextPersistenceFilter // 上下文管理HeaderWriterFilterCsrfFilter // 防止Csrf攻击,网关/多端类根据需要关闭LogoutFilter //退出管理UsernamePasswordAuthenticationFilter //核心,用户名及密码验证DefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterBasicAuthenticationFilter // Basic授权管理RequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterSessionManagementFilter // Session会话管理ExceptionTranslationFilterFilterSecurityInterceptor // 授权状态检查,最后兜底关口

解答最后两个问题:
3、我们自定义的鉴权类过滤器,是否会影响ApplicationFilterChain原始链的执行?
---- 自定义Security框架Filter不会影响原始链执行,但是也会在ApplicationFilterChain保存一份,只不过doFilter方法检测无需处理。我们自定义权限过滤器肯定是处理某一登录场景,URL是固定值。

5、我们定义的普通过滤器,会自动添加到授权过滤器列表吗?
---- 不会,授权过滤器链内容由HttpSecurity构建,除非我们手工将Filter添加到配置上下文中,否则只会在原始ApplicationFilterChain出现。

这篇关于【个人版】SpringBoot下Spring-Security核心概念解读【一】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/493687

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows