User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   SpringSecurity   ›   正文
SpringSecurity

SpringSecurity—基本原理

2020-07-16 01:48:38
1069  0 0
参考目录 隐藏
1) 1、框架接口设计
2) WebSecurityConfiguration中需要关注两个方法
3) 2、FilterChainProxy的创建过程
4) 2、FilterChainProxy的运行过程
5) 运行过程
6) 4、WebSecurity与HttpSecurity

阅读完需:约 12 分钟

上一篇笔记讲述了一些关键点和基本的流程,通过上一篇了解了基本的知识为下面做铺垫。

SpringSecurity—基础知识点与流程介绍

三句话解释框架原理

  1. 整个框架的核心是一个过滤器,这个过滤器名字叫springSecurityFilterChain类型是FilterChainProxy
  2. 核心过滤器里面是过滤器链(列表),过滤器链的每个元素都是一组URL对应一组过滤器
  3. WebSecurity用来创建FilterChainProxy过滤器,
    HttpSecurity用来创建过滤器链的每个元素。

1、框架接口设计

关注两个东西:建造者和配置器

框架的用法就是通过配置器对建造者进行配置

框架用法是写一个自定义配置类,继承WebSecurityConfigurerAdapter,重写几个configure()方法

WebSecurityConfigurerAdapter就是Web安全配置器的适配器对象

// 安全建造者
// 顾名思义是一个builder构造器,创建并返回一个类型为O的对象
public interface SecurityBuilder<O> {
    O build() throws Exception;
}

// 抽象安全建造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

    public final O build() throws Exception {
        // 限定build()只会进行一次!
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

    // 子类需要重写doBuild()方法
    protected abstract O doBuild() throws Exception;
}

// 配置后的抽象安全建造者
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {

    // 实现了doBuild()方法,遍历configurers进行init()和configure()。
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }
    // 它的子类HttpSecurity和WebSecurity都实现了它的performBuild()方法!!!
    protected abstract O performBuild() throws Exception;

    // 主要作用是将安全配置器SecurityConfigurer注入到属性configurers中,
    private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }
}
// 安全配置器,配置建造者B,B可以建造O
// 初始化(init)SecurityBuilder,且配置(configure)SecurityBuilder
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
    void init(B builder) throws Exception;
    void configure(B builder) throws Exception;
}


// Web安全配置器,配置建造者T,T可以建造web过滤器
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> 
        extends SecurityConfigurer<Filter, T> {
}

// Web安全配置器的适配器
// 配置建造者WebSecurity,WebSecurity可以建造核心过滤器
public abstract class WebSecurityConfigurerAdapter 
        implements WebSecurityConfigurer<WebSecurity> {
}


// 用于构建FilterChainProxy的建造者
public final class WebSecurity 
    extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
    implements
        SecurityBuilder<Filter>, ApplicationContextAware {
}

// 用于构建SecurityFilterChain的建造者
public final class HttpSecurity 
    extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
    implements 
        SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> {

}

总结:

  1. 看到建造者去看他的方法,build(); doBuild(); init(); configure(); performBuild();
  2. 看到配置器去看他的方法,init(); config();

从写MySecurityConfig时使用的@EnableWebSecurity注解开始看源码:

@EnableWebSecurity注解导入了三个类,重点关注WebSecurityConfiguration

WebSecurityConfiguration中需要关注两个方法

setFilterChainProxySecurityConfigurer()方法

  • 创建了WebSecurity建造者对象,用于后面建造FilterChainProxy过滤器

springSecurityFilterChain()方法

  • 调用WebSecurity.build(),建造出FilterChainProxy过滤器对象

————————————————————————————————————

2、FilterChainProxy的创建过程

回顾:

  1. 框架的核心是一个过滤器,这个过滤器名字叫springSecurityFilterChain,类型是FilterChainProxy
  2. WebSecurity和HttpSecurity都是建造者
  3. WebSecurity构建目标是FilterChainProxy对象
  4. HttpSecurity的构建目标仅仅是FilterChainProxy中的一个SecurityFilterChain。
  5. @EnableWebSecurity注解,导入了WebSecurityConfiguration类
  6. WebSecurityConfiguration中创建了建造者对象WebSecurity,和核心过滤器FilterChainProxy

从WebSecurityConfiguration开始

WebSecurityConfiguration中需要关注两个方法:

setFilterChainProxySecurityConfigurer()方法

  • 创建了WebSecurity建造者对象,用于后面建造FilterChainProxy过滤器

springSecurityFilterChain()方法

  • 调用WebSecurity.build(),建造出FilterChainProxy过滤器对象

WebSecurity的创建过程:setFilterChainProxySecurityConfigurer()方法

该方法负责收集配置类对象列表webSecurityConfigurers,并创建WebSecurity:

  1. @Value(“#{}”) 是SpEl表达式通常用来获取bean的属性或者调用bean的某个方法。
  2. 方法执行时,会先得到webSecurityConfigurers并排序(所有实现了WebSecurityConfigurerAdapter的配置类实例)
  3. new出websecurity对象,并使用Spring的容器工具初始化
  4. 判断webSecurityConfigurers内元素的@Order是否有相同,相同的order会抛异常。
    默认order等于LOWEST_PRECEDENCE = 2147483647(参考Integer order = AnnotationAwareOrderComparator.lookupOrder(config))
  5. 将WebSecurityConfigurerAdapter的子类apply()放入websecurity的List<SecurityConfigurer<O, B>> configurersAddedInInitializing中。

下图是通过AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法,获取所有实现WebSecurityConfigurer的配置类

FilterChainProxy的创建过程:springSecurityFilterChain()方法

在springSecurityFilterChain()方法中调用webSecurity.build()创建了FilterChainProxy。

PS:根据下面代码,我们可以知道如果创建的MySecurityConfig类没有被sping扫描到,
框架会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。

我们继续看FilterChainProxy的创建过程:

WebSecurity是一个建造者,所以我们去看这些方法build(); doBuild(); init(); configure(); performBuild();

build()方法定义在WebSecurity对象的父类AbstractSecurityBuilder中:

build()方法会调用WebSecurity对象的父类AbstractConfiguredSecurityBuilder#doBuild():

doBuild()先调用init();configure();等方法

我们上面已经得知了configurersAddedInInitializing里是所有的配置类对象

如下图,这里会依次执行配置类的configure();init()方法

doBuild()最后调用了WebSecurity对象的perfomBuild(),来创建了FilterChainProxy对象
performBuild()里遍历securityFilterChainBuilders建造者列表
把每个SecurityBuilder建造者对象构建成SecurityFilterChain实例
最后创建并返回FilterChainProxy

securityFilterChainBuilders建造者列表是什么时候初始化的呢

这时候要注意到WebSecurityConfigurerAdapter,这个类的创建了HttpSecurity并放入了securityFilterChainBuilders

WebSecurityConfigurerAdapter是一个安全配置器,我们知道建造者在performBuild()之前都会把循环调用安全配置器的init();configure();方法,然后创建HttpSecurity并放入自己的securityFilterChainBuilders里。

PS: 前面已经提到了,在WebSecurity初始化时,会依次将WebSecurityConfigurerAdapter的子类放入WebSecurity。

————————————————————————————————————

2、FilterChainProxy的运行过程

我们已经知道了Spring Security的核心过滤器的创建和原理,本次主要介绍核心过滤器FilterChainProxy是如何在tomcat的ServletContext中生效的。

ServletContext如何拿到FilterChainProxy的过滤器对象

我们都知道,Bean都是存在Spring的Bean工厂里的,
而且在Web项目中Servlet、Filter、Listener都要放入ServletContext中。

看下面这张图,ServletContainerInitializer接口提供了一个onStartup()方法,用于在Servlet容器启动时动态注册一些对象到ServletContext中。

官方的解释是:为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。

通过下图可知,Spring框架通过META-INF配置了SpringServletContainerInitializer类

SpringServletContainerInitializer实现了ServletContainerInitializer接口。

请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解.

根据Sevlet3.0规范,Servlet容器在调用onStartup()方法时,会以Set集合的方式注入WebApplicationInitializer的子类(包括接口,抽象类)。然后会依次调用WebApplicationInitializer的实现类的onStartup方法,从而起到启动web.xml相同的作用(添加servlet,listener实例到ServletContext中)。

Spring Security中的AbstractSecurityWebApplicationInitializer就是WebApplicationInitializer的抽象子类.

当执行到下面的onStartup()方法时,会调用insertSpringSecurityFilterChain()

将类型为FilterChainProxy名称为springSecurityFilterChain的过滤器对象用DelegatingFilterProxy包装,然后注入ServletContext

DelegatingFilterProxy相关笔记:

Spring—DelegatingFilterProxy的作用与用法 (过滤器授权代理)

运行过程

请求到达的时候,FilterChainProxy的dofilter()方法内部,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证或授权。

public class FilterChainProxy extends GenericFilterBean {

    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    private HttpFirewall firewall = new StrictHttpFirewall();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        filterChainValidator.validate(this);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                doFilterInternal(request, response, chain);
            }
            finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        }
        else {
            doFilterInternal(request, response, chain);
        }
    }

    private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);

        // 根据当前请求,获得一组过滤器链
        List<Filter> filters = getFilters(fwRequest);

        if (filters == null || filters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                        + (filters == null ? " has no matching filters": " has an empty filter list"));
            }
            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
            return;
        }

        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        // 请求依次经过这组滤器链
        vfc.doFilter(fwRequest, fwResponse);
    }

    /**
     * 根据Request请求获得一个过滤器链
     */
    private List<Filter> getFilters(HttpServletRequest request) {
        for (SecurityFilterChain chain : filterChains) {
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
        return null;
    }

    /**
     * 根据URL获得一个过滤器链
     */
    public List<Filter> getFilters(String url) {
        return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
                .getRequest())));
    }

    /**
     * 返回一个过滤器链
     */
    public List<SecurityFilterChain> getFilterChains() {
        return Collections.unmodifiableList(filterChains);
    }

    // 过滤器链内部类
    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<Filter> additionalFilters;
        private final FirewalledRequest firewalledRequest;
        private final int size;
        private int currentPosition = 0;

        private VirtualFilterChain(FirewalledRequest firewalledRequest,
                FilterChain chain, List<Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
            this.size = additionalFilters.size();
            this.firewalledRequest = firewalledRequest;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }

    public interface FilterChainValidator {
        void validate(FilterChainProxy filterChainProxy);
    }

    private static class NullFilterChainValidator implements FilterChainValidator {
        @Override
        public void validate(FilterChainProxy filterChainProxy) {
        }
    }

}

————————————————————————————————————

4、WebSecurity与HttpSecurity

前面我们已经分析了Spring Security的核心过滤器FilterChainProxy的创建和运行过程,认识了建造者和配置器的作用。

现在我们知道WebSecurity作为一个建造者就是用来创建核心过滤器FilterChainProxy实例的。

WebSecurity在初始化的时候会扫描WebSecurityConfigurerAdapter配置器适配器的子类(即生成HttpSecurity配置器)。

所有的配置器会被调用init();configure();初始化配置,其中生成的每个HttpSecurity配置器都代表了一个过滤器链。

本篇要说的就是HttpSecurity作为一个建造者,是如何去建造出SecurityFilterChain过滤器链实例的!

PS:如果有多个WebSecurityConfigurerAdapter配置器适配器的子类,会产生多个SecurityFilterChain过滤器链实例。Spring Security Oauth2的拓展就是这么做的。

spring security 怎么创建的过滤器

我们已经知道了springSecurityFilterChain(类型为FilterChainProxy)是实际起作用的过滤器链,DelegatingFilterProxy起到代理作用。

我们创建的MySecurityConfig继承了WebSecurityConfigurerAdapter。WebSecurityConfigurerAdapter就是用来创建过滤器链,重写的configure(HttpSecurity http)的方法就是用来配置HttpSecurity的。

protected void configure(HttpSecurity http) throws Exception {
        http
            .requestMatchers() // 指定当前`SecurityFilterChain`实例匹配哪些请求
                .anyRequest().and()
            .authorizeRequests() // 拦截请求,创建FilterSecurityInterceptor
                .anyRequest().authenticated() // 在创建过滤器的基础上的一些自定义配置
                .and() // 用and来表示配置过滤器结束,以便进行下一个过滤器的创建和配置
            .formLogin().and() // 设置表单登录,创建UsernamePasswordAuthenticationFilter
            .httpBasic(); // basic验证,创建BasicAuthenticationFilter
}

上面的configure(HttpSecurity http)方法内的配置最终内容主要是Filter的创建。

http.authorizeRequests()、http.formLogin()、http.httpBasic()分别创建了ExpressionUrlAuthorizationConfigurer,FormLoginConfigurer,HttpBasicConfigurer。在三个类从父级一直往上找,会发现它们都是SecurityConfigurer建造器的子类。SecurityConfigurer中又有configure()方法。该方法被子类实现就用于创建各个过滤器,并将过滤器添加进HttpSecurity中维护的装有Filter的List中,比如HttpBasicConfigurer中的configure方法,源码如下:

HttpSecurity作为建造者会把根据api把这些配置器添加到实例中

这些配置器中大都是创建了相应的过滤器,并进行配置,最终在HttpSecurity建造SecurityFilterChain实例时放入过滤器链

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594496浏览

随机文章
ElasticSearch—字段类型详解(十一)
5年前
SpringCloud—Hystrix(三)服务降级与异常处理
5年前
SpringMVC—HandlerMethodArgumentResolver参数处理器
3年前
Java—P6Spy数据库SQL记录
1年前
Kotlin-函数进阶—高阶函数(十五)
4年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594496 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付