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

SpringSecurity原理解析(五):HttpSecurity 类处理流程

1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别

      通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件

      的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,

      SpringSecurity 配置文件如下所示:

<!-- SpringSecurity配置文件 --><!--auto-config:表示自动加载SpringSecurity的配置文件use-expressions:表示使用Spring的EL表达式--><security:http auto-config="true" use-expressions="true"><!--定义匿名访问,跳转到登录页面 --><security:intercept-url pattern="/login.jsp" access="permitAll()"/><!--拦截资源pattern="/**" 拦截所有的资源access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER 这个角色可以访问资源--><security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" ></security:intercept-url><!--配置认证(用户登录)信息,覆盖security 默认的登录页面login-page:登录页面地址login-processing-url:登录的请求urldefault-target-url:登录成功皇后的目标地址authentication-failure-url:登录校验失败后的地址--><security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/home.jsp" authentication-failure-url="/error.jsp"/><!--开启csrf校验 --><security:csrf disabled="true"/><!--开启“记住我” 登录用户缓存功能,该功能默认是关闭的,需要手动开启remember-me-parameter 是登录页面配置的 “记住我” 功能的属性名称,如login.jsp 中的 "remember-me"token-validity-seconds 设置 “记住我” 登录的数据保存的超时时间,注意:当前 这种配置只是把“登录数据” 临时保存在页面的Cookie(token) 中,保存在页面中的数据安全性很差,很容故意被盗取;为了解决这个问题 spring security提供了把“记住我” 功能的数据保存到数据库中,需要在 <security:remember-me>中添加配置属性 data-source-ref,并 指定数据源,如:data-source-ref="dataSource"--><security:remember-metoken-validity-seconds="1200"data-source-ref="dataSource"remember-me-parameter="remember-me"/><!--自定义错误页面 --><security:access-denied-handler error-page="error.jsp"/></security:http><!--向IOC容器注入一个bean--><bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder"/><!-- 认证用户信息 --><security:authentication-manager><security:authentication-provider user-service-ref="userServiceImpl"><!--指向自己定义的认证service,在service 中根据登录用户与数据库的数据进行用户认证处理,这样保密性比较好 --><!--<security:user-service >设置一个账号 zhangsan 密码123 {noop} 表示不加密 具有的角色是  ROLE_USER<security:user name="zhangsan" authorities="ROLE_USER" password="{noop}123" ></security:user><security:user name="lisi" authorities="ROLE_USER" password="{noop}123456" ></security:user></security:user-service>--><!--引入用户认证密码加密方式 --><security:password-encoder ref="bCryptPasswordEncoder"></security:password-encoder></security:authentication-provider></security:authentication-manager>

      也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在

      SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,通过配置类的方式来配置

      SpringSecurity。SpringSecurity配置类需要继承类 WebSecurityConfigurerAdapter,并重写

      configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 方法;

       SpringSecurity 配置类如下所示:     

/*** SpringSecurity的配置文件* WebSecurityCofniguration中 @Bean注解 把 FilterChainProxy 注入到了容器中 而且名称为springSecurityFilterChain* 而 FilterChainProxy 对象是通过 WebSecurity 构建的** @EnableWebSecurity*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Autowiredprivate BCryptPasswordEncoder bCryptPasswordEncoder;@Autowiredprivate PersistentTokenRepository persistentTokenRepository;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService)// 数据库认证,绑定需要执行用户认证操作的service.passwordEncoder(bCryptPasswordEncoder); // 设置加密处理的方式//auth.inMemoryAuthentication().withUser("root").password("123")}/*** 容器中注入 BCryptPasswordEncoder* @return*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** HttpSecurity 相当于 SpringSecurity配置文件中 http 标签** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//匿名访问资源.antMatchers("/login.html","/css/**","/img/**")// 配置需要放过的资源,表示前边 antMatchers 配置的资源都需要放过.permitAll().antMatchers("/**")//认证的资源及所具备的权限.hasAnyRole("USER").anyRequest()//表示所有请求都需要认证.authenticated()//需要认证//and() 返回一个HttpSecurity.and()// 配置登录表单相关的信息.formLogin()// 指定自定义的登录页面//todo 注意://      对于前后端分离的项目,不需要指定跳转的页面,只需要 loginProcessingUrl//      设置请求资源url就行了.loginPage("/login.html") //认证表单相关信息.loginProcessingUrl("/login") // 表单提交的登录地址.defaultSuccessUrl("/home.html")//表示上边与form表单提交得资源都要放过.permitAll().and().rememberMe() // 放开 记住我 的功能.tokenRepository(persistentTokenRepository) // 持久化.and()//csrf设置.csrf().disable();HttpSecurity http1 = http.authorizeRequests()// 配置需要放过的资源.antMatchers("/login.html", "/css/**", "/img/**").permitAll()//表示放过前边 antMatchers 配置得资源.antMatchers("/**").hasAnyRole("USER").anyRequest().authenticated().and();}/*** 向Spring容器中注入 PersistentTokenRepository 对象* @param dataSource* @return*/@Beanpublic PersistentTokenRepository persistentTokenRepository(DataSource dataSource){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();//绑定数据源tokenRepository.setDataSource(dataSource);return tokenRepository;}public static void main(String[] args) {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = "admin";// 每次都会生成一个随机的salt,同一个明文加密多次编码得到的密文其实都不一样System.out.println(encoder.encode(password));System.out.println(encoder.encode(password));System.out.println(encoder.encode(password));}
}

       在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置

      文件中定义的<security:http>标签,而。

      通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过

     标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?

2、HttpSecurity 类的处理过程

2.1、HttpSecurity 类图:

         

         由该类图可以清晰得发现,HttpSecurity 继承了父类 AbstractConfiguredSecurityBuilder

         并实现了接口 SecurityBuilder 和 HttpSecurityBuilder

         HttpSecurity 类的定义如下:

                

2.2、SecurityBuilder 接口

         SecurityBuilder 定义如下:

public interface SecurityBuilder<O> {//构建 SecurityBuilder 指定泛型类型的对象O build() throws Exception;
}

         由接口 SecurityBuilder 的定义可以发现,SecurityBuilder 接口只提供了一个 build() 方法,用

         来构建 SecurityBuilder 泛型指定类型的bean对象。

         结合 HttpSecurity 类中实现 SecurityBuilder 接口时的泛型是什么,就知道在 HttpSecurity 类

         中 SecurityBuilder 是用来创建什么对象,HttpSecurity 定义如下:

                   

          由 HttpSecurity 的定义可以发现,在 HttpSecurity 类中 SecurityBuilder 指定的泛型是

          DefaultSecurityFilterChain,DefaultSecurityFilterChain 是拦截器链SecurityFilterChain

          一个默认实现,所以 DefaultSecurityFilterChain 是一个拦截器链,所以在 HttpSecurity

          中,SecurityBuilder 是用来创建拦截器链的。

2.2.1、SecurityBuilder.build() 方法的实现

            下面看下 SecurityBuilder.build() 的实现过程,及连接器链的创建过程

             SecurityBuilder 的默认实现是类 AbstractSecurityBuilder

             SecurityBuilder.build() 方法的实现如下:

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {//CAS类型private AtomicBoolean building = new AtomicBoolean();//返回创建的O的对象private O object;public AbstractSecurityBuilder() {}//创建泛型O的对象public final O build() throws Exception {//基于CAS,保证在整个环境中O只被创建一次if (this.building.compareAndSet(false, true)) {//真正创建泛型O的对象this.object = this.doBuild();return this.object;} else {throw new AlreadyBuiltException("This object has already been built");}}//返回O的对象public final O getObject() {if (!this.building.get()) {throw new IllegalStateException("This object has not been built");} else {return this.object;}}//抽象方法,由子类实现protected abstract O doBuild() throws Exception;
}

             由 AbstractSecurityBuilder 的定义可以发现,真正创建泛型O(在 HttpSecurity 中O是

             拦截器链 DefaultSecurityFilterChain )的对象是在doBuild 发给发中完成,而doBuild是

             一个抽象方法,由子类 AbstractConfiguredSecurityBuilder 实现,

             doBuild 方法实现如下:

protected final O doBuild() throws Exception {synchronized(this.configurers) {this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;this.beforeInit();//执行当前类的init方法进行初始化操作this.init();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;this.beforeConfigure();this.configure();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;//获取构建的对象,上面的方法可以先忽略O result = this.performBuild();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;return result;}}

             performBuild() 是一个抽象方法,由AuthenticationManagerBuilder、HttpSecurity、

             WebSecurity 三个子类实现,在这里我们应该看 HttpSecurity 中的实现

             HttpSecurity.performBuild() 方法的实现如下:        

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

             DefaultSecurityFilterChain 构造方法如下:

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {if (!filters.isEmpty()) {logger.info(LogMessage.format("Will not secure %s", requestMatcher));} else {logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));}//绑定请求匹配规则this.requestMatcher = requestMatcher;//绑定过滤器集合this.filters = new ArrayList(filters);}

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

                     

             对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心

            默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析

2.3、AbstractConfiguredSecurityBuilder

        AbstractConfiguredSecurityBuilder 是一个抽象类,也可以看成是 SecurityBuilder 接口的

        实现,AbstractConfiguredSecurityBuilder 类图如下所示:

               

               

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

       AbstractConfiguredSecurityBuilder 对应的三个实现类,如下所示:

                   

2.3.1、BuildState

            AbstractConfiguredSecurityBuilder 中定义了一个枚举类BuildState,将整个构建过程

            分为 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.3.2、AbstractConfiguredSecurityBuilder 常见方法

2.3.2.1、add() 方法

              add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到

             configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,

             key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当

             需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,

              这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。

              add 方法代码如下:        

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {Assert.notNull(configurer, "configurer cannot be null");//configurer必须是 SecurityConfigurer 的子类Class<? extends SecurityConfigurer<O, B>> clazz = configurer.getClass();synchronized(this.configurers) {if (this.buildState.isConfigured()) {throw new IllegalStateException("Cannot apply " + configurer + " to already built object");} else {List<SecurityConfigurer<O, B>> configs = null;if (this.allowConfigurersOfSameType) {configs = (List)this.configurers.get(clazz);}List<SecurityConfigurer<O, B>> configs = configs != null ? configs : new ArrayList(1);((List)configs).add(configurer);this.configurers.put(clazz, configs);if (this.buildState.isInitializing()) {this.configurersAddedInInitializing.add(configurer);}}}}//获取指定的配置类
@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);}

2.3.2.2、doBuild()方法      

@Overrideprotected 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);}}

2.4、HttpSecurity

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

         HttpSecurity 部分方法列表如下:

                

         HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们

          就不一一介绍 了。每个配置方法的结尾都会调用一次 getOrApply 方法,getOrApply

         方法是做什么的?

         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 方法.

          addFilter 方法如下所示:

@Overridepublic 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 变量中。

3、总结

      这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简

      单的重 复的 xxxConfigurer 配置了

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 汽车无钥匙启动功能工作原理
  • vscode任务配置之tasks.json
  • 一位10块!餐厅的白开水,也开始收钱了……
  • 〖open-mmlab: MMDetection〗解析文件:mmdet/models/roi_heads/bbox_heads/bbox_head.py
  • 【安全系列--处理挖矿】
  • 解析主子格式的 csv
  • 基于Java+ssm+jsp开发的相亲交友网站管理系统
  • Oracle rman 没有0级时1级备份和0级大小一样,可以用来做恢复 resetlogs后也可以
  • 源代码如何防泄漏?用对软件真的很重要!
  • BRAS介绍
  • 中间件的学习理解总结
  • Go语言中的队列与栈:基础与实践
  • C语言深入理解指针四(17)
  • 国外也开始流行“卷”了吗
  • 抖音ip属地怎么改变到别的城市
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • 【Leetcode】104. 二叉树的最大深度
  • Angular数据绑定机制
  • css布局,左右固定中间自适应实现
  • ES6 学习笔记(一)let,const和解构赋值
  • Golang-长连接-状态推送
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • JS数组方法汇总
  • Next.js之基础概念(二)
  • python 学习笔记 - Queue Pipes,进程间通讯
  • webgl (原生)基础入门指南【一】
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 好的网址,关于.net 4.0 ,vs 2010
  • 那些被忽略的 JavaScript 数组方法细节
  • elasticsearch-head插件安装
  • 阿里云服务器如何修改远程端口?
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​字​节​一​面​
  • #HarmonyOS:软件安装window和mac预览Hello World
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (7) cmake 编译C++程序(二)
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (STM32笔记)九、RCC时钟树与时钟 第二部分
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (五十)第 7 章 图(有向图的十字链表存储)
  • .bashrc在哪里,alias妙用
  • .bat文件调用java类的main方法
  • .DFS.
  • .net CHARTING图表控件下载地址
  • .NET CORE Aws S3 使用
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .net对接阿里云CSB服务
  • 。Net下Windows服务程序开发疑惑
  • /bin/bash^M: bad interpreter: No such file ordirectory