深入理解HttpSecurity的设计

简介: 介绍了基于配置文件的使用方式以及实现细节,如下:

@[TOC]

HttpSecurity的应用

在[上文]介绍了基于配置文件的使用方式以及实现细节,如下:

image.png

也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于在配置文件中定义的http标签。使用方式如下。

image.png

通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式上文已经分析过了,是通过标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?

HttpSecurity的类图结构

image.png

可以看出HttpSecurity的类图结构相对比较简单,继承了一个父类,实现了两个接口。分别来看看他们的作用是什么。

SecurityBuilder接口

先来看看SecurityBuilder接口,通过字面含义可以发现这是一个帮我们创建对象的工具类。

public interface SecurityBuilder<O> {

    /**
     * Builds the object and returns it or null.
     * @return the Object to be built or null if the implementation allows it.
     * @throws Exception if an error occurred when building the Object
     */
    O build() throws Exception;

}

通过源码可以看到在SecurityBuilder中给我们提供了一个build()方法。在接口名称处声明了一个泛型,而build()方法返回的正好是这个泛型的对象,其实就很好理解了,也就是SecurityBuilder会创建指定类型的对象。结合HttpSecurity中实现SecurityBuilder接口时指定的泛型可以看出创建的具体对象是什么类型。

image.png

可以看出SecurityBuilder会通过build方法给我们创建一个DefaultSecurityFilterChain对象。也就是拦截请求的那个默认的过滤器链对象。

image.png

进入到doBuild()方法,会进入到AbstractConfiguredSecurityBuilder中的方法

    @Override
    protected final O doBuild() throws Exception {
        synchronized (this.configurers) {
            this.buildState = BuildState.INITIALIZING;
            beforeInit();
            init();
            this.buildState = BuildState.CONFIGURING;
            beforeConfigure();
            configure();
            this.buildState = BuildState.BUILDING;
             // 获取构建的对象,上面的方法可以先忽略
            O result = performBuild();
            this.buildState = BuildState.BUILT;
            return result;
        }
    }

进入到HttpSecurity中可以查看performBuild()方法的具体实现。

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

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

image.png

对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心默认的过滤器链中的过滤器是哪来的。

AbstractConfiguredSecurityBuilder

然后再看看AbstractConfiguredSecurityBuilder这个抽象类,他其实是SecurityBuilder的实现,在这儿需要搞清楚他们的关系。

image.png

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

然后对应的三个实现类。

image.png

首先 AbstractConfiguredSecurityBuilder 中定义了一个枚举类,将整个构建过程分为 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;
        }

    }

通过这些状态来管理需要构建的对象的不同阶段。

add方法

AbstractConfiguredSecurityBuilder中方法概览

image.png

先来看看add方法。

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
        Assert.notNull(configurer, "configurer cannot be null");
        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
                .getClass();
        synchronized (this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            }
            List<SecurityConfigurer<O, B>> configs = null;
            if (this.allowConfigurersOfSameType) {
                configs = this.configurers.get(clazz);
            }
            configs = (configs != null) ? configs : new ArrayList<>(1);
            configs.add(configurer);
            this.configurers.put(clazz, configs);
            if (this.buildState.isInitializing()) {
                this.configurersAddedInInitializing.add(configurer);
            }
        }
    }

    /**
     * Gets all the {@link SecurityConfigurer} instances by its class name or an empty
     * List if not found. Note that object hierarchies are not considered.
     * @param clazz the {@link SecurityConfigurer} class to look for
     * @return a list of {@link SecurityConfigurer}s for further customization
     */
    @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);
    }

add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到 configurers中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,key 是配置类的 class,value 是一个集合,集合里边放着 xxxConfigure 配置类。当需要对这些配置类进行集中配置的时候,会通过 getConfigurers 方法获取配置类,这个获取过程就是把 LinkedHashMap 中的 value 拿出来,放到一个集合中返回。

doBuild方法

然后看看doBuild方法中的代码

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

HttpSecurity

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

image.png

HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。每个配置方法的结尾都会来一句 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 方法.

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

相关文章
|
JavaScript
源码学习:Vite中加载环境变量(loadEnv)的实现
源码学习:Vite中加载环境变量(loadEnv)的实现
7916 0
|
SQL
SpringCloud - Feign调用返回对象不为null,但属性全为null
SpringCloud - Feign调用返回对象不为null,但属性全为null
2056 0
|
监控 负载均衡 安全
微服务(五)-服务网关zuul(一)
微服务(五)-服务网关zuul(一)
|
11月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
5818 13
Spring Boot 3 集成 Spring Security + JWT
|
API 定位技术
查IP[查指定IP归属地]免费API接口教程
该API用于查询指定IPv4地址的归属地信息,支持POST和GET请求。需提供用户ID和KEY,可选填查询IP,默认为请求接口IP。返回信息包括状态码、地理位置及运营商等。示例请求和响应详见文档。
2106 3
|
应用服务中间件 PHP Apache
HAProxy的高级配置选项-ACL篇之匹配访问路径案例
这篇文章介绍了HAProxy的高级配置选项,特别是如何使用ACL(访问控制列表)匹配访问路径以实现不同请求路径的流量分发到不同后端服务器的案例,通过实战配置展示了如何基于URL路径将请求定向到处理静态或动态内容的服务器。
322 5
HAProxy的高级配置选项-ACL篇之匹配访问路径案例
|
安全 搜索推荐 Java
SpringSecurity扩展用户身份信息(UserDetails)的方式
通过上述步骤,你就能在Spring Security中扩展 `UserDetails`,进而实现更加个性化和复杂的用户认证和授权机制。记住,在添加更多字段时,保持系统安全性的同时,也需要考虑到用户隐私的保护。
1428 1
|
Java 开发者 Spring
|
存储 SQL 缓存
MySQL 配置文件 my.cnf / my.ini 逐行详解
充分理解 MySQL 配置文件中各个变量的意义对我们有针对性的优化 MySQL 数据库性能有非常大的意义。我们需要根据不同的数据量级,不同的生产环境情况对 MySQL 配置文件进行优化。Windows 和 Linux 下的 MySQL 配置文件的名字和存放位置都是不同的,WIndows 下 MySQL 配置文件是 `my.ini` 存放在 MySQL 安装目录的根目录下;Linux 下 MySQL 配置文件是 `my.cnf` 存放在 `/etc/my.cnf`、`/etc/mysql/my.cnf`。我们也可以通过 `find` 命令进行查找。
37681 2
|
Java 数据库 微服务
使用OpenFeign进行服务调用
本文档介绍如何在微服务架构中使用Spring Cloud的OpenFeign进行服务间的远程调用。首先,需在项目中引入OpenFeign及其负载均衡器依赖。接着,通过`@EnableFeignClients`启用Feign客户端功能,并定义客户端接口以声明远程服务调用逻辑。为确保启动类能正确扫描到这些接口,需指定`basePackages`属性。最后,演示了如何在购物车服务中利用Feign客户端接口调用商品服务,以实现跨服务的数据整合和查询。Feign通过动态代理机制简化了HTTP请求的发起过程,使服务间交互更为直观和便捷。
478 0

热门文章

最新文章