【SpringSecurity源码】过滤器链加载流程

news2024/12/23 3:00:25

theme: smartblue
highlight: a11y-dark

一、前言及准备

1.1 SpringSecurity过滤器链简单介绍

在Spring Security中,过滤器链(Filter Chain)是由多个过滤器(Filter)组成的,这些过滤器按照一定的顺序对进入应用的请求进行处理。每个过滤器可以执行不同的安全操作,如身份验证、授权、安全上下文的建立等。

过滤器是一种典型的AOP思想,我们将通过源码分析这些过滤器如何被加载以及组成过滤器链。

1.2 SpringSecurity配置类

该类主要对SpringSecurity进行一系列配置,后续过滤器链的初始化和加载也是基于这个配置类。当前配置类仅供参考。注意里面两个很重要的方法 #configure(WebSecurity web)以及#configure(WebSecurity web),他们对过滤器链的初始化很重要。

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 自定义登录成功或失败处理器,退出登录处理器
    @Autowired
    private MyAuthenticationService myAuthenticationService;

    // 自定义验证码过滤器
    @Autowired
    private ValidateCodeFilter validateCodeFilter;

    // 自定义权限不足处理
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    // 权限相关Service
    @Autowired
    private PermissionService permissionService;


    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/code/**");
    }

    /**
     * http请求方法
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 加入用户名密码验证过滤器的前面
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);

        // 查询数据库所有权限列表
        List<Permission> list = permissionService.list();
        for (Permission permission : list) {
            // 添加请求权限
            http.authorizeRequests().antMatchers(permission.getPermissionUrl())
                    .hasAuthority(permission.getPermissionTag());
        }

        // 设置权限不足的信息
        http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);

        http.formLogin()// 开启表单认证
                .loginPage("/toLoginPage")// 自定义登录页面
                .loginProcessingUrl("/login")//表单提交的路径
                .usernameParameter("username")
                .passwordParameter("password")//自定义input的name值
                .successForwardUrl("/")//登录成功之后跳转的路径
                .successHandler(myAuthenticationService)
                .failureHandler(myAuthenticationService)//登录成功或者失败的处理
                .and().logout().logoutUrl("/logout")
                .logoutSuccessHandler(myAuthenticationService)
                .and().rememberMe()//开启记住我功能
                .tokenValiditySeconds(1209600)//token失效时间 默认是2周
                .rememberMeParameter("remember-me")//自定义表单input值
                .tokenRepository(getPersistentTokenRepository())
                .and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面
                .anyRequest().authenticated();
                
        //关闭csrf防护
        http.csrf().disable();

        //加载同源域名下iframe页面
        http.headers().frameOptions().sameOrigin();

        // 开启跨域支持
        http.cors().configurationSource(corsConfigurationSource());


    }
  } 

1.3 过滤器链加载方法入口

Spring boot启动中会加载spring.factories文件, 在文件中有对应针对Spring Security的过滤器链
的配置信息,所以spring.factories来找过滤器链加载方法入口

image.png

所以我们的过滤器链加载方法入口位于org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()

二、 源码导读

SpringSecurity的过滤器链创建本质就是读取SecurityConfig配置类,并且通过核心配置方法:#configure(WebSecurity web)以及#configure(WebSecurity web)生成一个个配置对象,最终每个配置对象会转换成一个过滤器对象,这些过滤器对象组成过滤器链。

前面已经提到了,配置类中的 #configure(WebSecurity web)以及#configure(WebSecurity web)非常重要。他们将分别加载到一个HttpSecurity对象和一个名为ignoredRequests的ArrayList之中,
并且过滤器链也是由这两部分转换后组成

2.1 SpringSecurity配置信息的加载

由前面的内容我们已经定位到过滤器链加载的方法入口位于:
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = webSecurityConfigurers != null
          && !webSecurityConfigurers.isEmpty();
    if (!hasConfigurers) {
       WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
             .postProcess(new WebSecurityConfigurerAdapter() {
             });
       webSecurity.apply(adapter);
    }
    // 加载方法
    return webSecurity.build();
}

我们从该方法的webSecurity.build()按照下图一路点击,最终会进入到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
方法,该方法是加载配置和过滤器链的主要方法

image.png

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()如下

@Override
protected final O doBuild() throws Exception {
    synchronized (configurers) {
       buildState = BuildState.INITIALIZING;

       beforeInit();
       
       // 【1】.初始化,主要构建HttpSecurity对象以及内部的配置对象
       init();

       buildState = BuildState.CONFIGURING;
       
       
       beforeConfigure();
       
       // 【2】ignoredRequests集合的构建
       configure();

       buildState = BuildState.BUILDING;

       // 【3】组装过滤器链
       O result = performBuild();

       buildState = BuildState.BUILT;

       return result;
    }

可以看到,当前的configurers加载的就是我们自定义的配置类

image.png

2.1.1 构建带有配置信息的HttpSecurity对象

我们从上述的doBuild()方法体中点击init()方法进入其内部

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#init()

private void init() throws Exception {

    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    
    
    for (SecurityConfigurer<O, B> configurer : configurers) {
    // 构建HttpSecurity对象
       configurer.init((B) this);
    }

    for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
       configurer.init((B) this);
    }
}

接着点击构建HttpSecurity的对象的方法configurer.init()方法

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init(),发现真正构建HttpSecurity的方法其实是 getHttp()

public void init(final WebSecurity web) throws Exception {
    // 真正构建HttpSecurity的方法
    final HttpSecurity http = getHttp();
    
    // 将HttpSecurity对象作为SecurityFilterChainBuilder对象返回
    web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
       FilterSecurityInterceptor securityInterceptor = http
             .getSharedObject(FilterSecurityInterceptor.class);
       web.securityInterceptor(securityInterceptor);
    });
}

点击进入 getHttp()

protected final HttpSecurity getHttp() throws Exception {
    
    /**
        省略其他
    **/
    
    // 【1】.新建一个HttpSecurity对象
    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
          sharedObjects);
    if (!disableDefaults) {
       // 【2】.设置HttpSecurity对象中configurers配置对象以及filter过滤器对象
       http
          .csrf().and() 
          .addFilter(new WebAsyncManagerIntegrationFilter()) 
          .exceptionHandling().and()
          .headers().and()
          .sessionManagement().and()
          .securityContext().and()
          .requestCache().and()
          .anonymous().and()
          .servletApi().and()
          .apply(new DefaultLoginPageConfigurer<>()).and()
          .logout();
        /**
            省略
        **/
    }
    
    
    // 【3】.执行SecurityConfig中的configure(HttpSecurity http)方法
    configure(http);

    return http;
}

观察上述代码我们发现,首先通过new HttpSecurity()新建了一个HttpSecurity对象,并且在下方设置HttpSecurity对象中configurers属性以及filter属性。

当执行完下面代码时,即【2】处的代码

        http
          .csrf().and() //【添加CsrfConfigurer】
          .addFilter(new WebAsyncManagerIntegrationFilter())  // 【添加 WebAsyncManagerIntegrationFilter】
          .exceptionHandling().and() // 【添加 ExceptionHandlingConfigurer】
          .headers().and() // 【添加 HeadersConfigurer】
          .sessionManagement().and()// 【添加SessionManagementConfigurer】
          .securityContext().and()// 【添加SecurityContextConfigurer】
          .requestCache().and()// 【添加RequestCacheConfigurer】
          .anonymous().and()// 【添加AnonymousConfigurer】
          .servletApi().and()// 【添加ServletApiConfigurer】
          .apply(new DefaultLoginPageConfigurer<>()).and()//【添加DefaultLoginPageConfigurer】
          .logout(); // 【添加LogoutConfigure】r

HttpSecurity对象中configurers属性以及filter如下,此时size分别为 101

image.png
image.png

执行了【2】的代码后会执行【3】的代码:
configure(http);
该方法会调用SecurityConfig配置类文件的configure()方法,由于此时传入的HttpSecurity对象,所以调用的
SecurityConfig配置类文件中的configure(HttpSecurity http),代码如下(注意该部分为自定义的配置文件中方法,需要伙伴自己去编写,根据自己需求进行配置。当前仅供参考!):

@Override
protected void configure(HttpSecurity http) throws Exception {

    // 加入用户名密码验证过滤器的前  
    【添加ValidateCodeFilter】
    http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);

    // 查询数据库所有权限列表
    List<Permission> list = permissionService.list();
    
    【添加ExpressionUrlAuthorizationConfigurer】
    for (Permission permission : list) {
        // 添加请求权限
        http.authorizeRequests().antMatchers(permission.getPermissionUrl())
                .hasAuthority(permission.getPermissionTag());
    }

    // 设置权限不足的信息 
    http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);

    http.formLogin()// 开启表单认证 【添加FormLoginConfigurer】
            .loginPage("/toLoginPage")// 自定义登录页面
            .loginProcessingUrl("/login")//表单提交的路径
            .usernameParameter("username")
            .passwordParameter("password")//自定义input的name值
            .successForwardUrl("/")//登录成功之后跳转的路径
            .successHandler(myAuthenticationService)
            .failureHandler(myAuthenticationService)//登录成功或者失败的处理
            .and().logout().logoutUrl("/logout")
            .logoutSuccessHandler(myAuthenticationService)
            .and().rememberMe()//开启记住我功能 【添加 RememberMeConfigurer】
            .tokenValiditySeconds(1209600)//token失效时间 默认是2周
            .rememberMeParameter("remember-me")//自定义表单input值
            .tokenRepository(getPersistentTokenRepository())
            .and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面
            .anyRequest().authenticated();
    //关闭csrf防护
    // 【移除CsrfConfigurer】
    http.csrf().disable();

    //加载同源域名下iframe页面
    http.headers().frameOptions().sameOrigin();

    // 开启跨域支持
    // 【添加CorsConfigurer】
    http.cors().configurationSource(corsConfigurationSource());

}

执行完该方法后,configurers属性以及filter属性如下,size变为132

image.png
image.png

HttpSecurity对象构建完成!,之后configurers中每个Configurer对象最终都会转换为Filter对象

2.1.2 列表集合 ignoredRequests 的创建

当执行完init()后,HttpSecurity对象构建完成,并且存放在一个叫做securityFilterChainBuilders的集合对象中。

image.png

此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild(),在执行init()方法之后会有一个configure()方法,点击进去可以看到:

private void configure() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

    for (SecurityConfigurer<O, B> configurer : configurers) {
      // 【加载配置类SecurityConfig中的configure(WebSecurity web)方法】
       configurer.configure((B) this);
    }
}

注意看

configurer.configure((B) this);

这句代码,此时的this是一个WebSecurity对象
image.png

仔细想想,在配置类文件中,不正是有一个#configure(WebSecurity web)配置方法吗?没错,这句代码正是执行了配置文件中的该方法。

#configure(WebSecurity web)方法体:

image.png

执行完这一行代码之后,ignoredRequests变量已经被赋值,而且变量中的值是和上面配置方法中配置对应的。

image.png

OK,此时SpringSecurity配置信息的加载就已经完成,接下来就是将这些配置信息转换成过滤器并组成过滤器链

2.2 过滤器链的加载

加载完配置文件后,我们的配置信息分别被存放在两部分ignoredRequestssecurityFilterBulders之中,下面再来看看这两部分是如何构建成过滤器链的。

2.2.1 ignoredRequests 转换成过滤器链

image.png
此时我们再次回到此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
找到构造过滤器链的方法performBuild()

org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild()

@Override
protected Filter performBuild() throws Exception {
     /**
         忽略部分代码
       **/
    
    【1】计算过滤器链长度
    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
    
    
    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
          chainSize);
  
    【2】将ignoredRequests集合中的对象转为过滤器加入到过滤器链中
    for (RequestMatcher ignoredRequest : ignoredRequests) {
       securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }
    
    【3】将securityFilterChainBuilder转为过滤器加入到过滤器链中
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
       securityFilterChains.add(securityFilterChainBuilder.build());
    }
   /**
         忽略部分代码
       **/
    }
    /**
         忽略部分代码
       **/
    return result;
}

【1】处代码计算过滤器链长度chainSize,Debug中计算结果如下

image.png

【2】处代码将ignoredRequests集合中的对象转为过滤器加入到过滤器链securityFilterChains
中,执行结果如下

image.png

ignoredRequests转换成过滤器链完成

【3】处的代码就是 securityFilterBulders的的处理。下面接着讲。

2.2.2 securityFilterBulders 转换成过滤器链

在上面的org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild() 方法中通过观察看到,处理securityFilterBulders代码就一句话

securityFilterChains.add(securityFilterChainBuilder.build());

我们从securityFilterChainBuilder.build(),按照下面顺序一路点击:

image.png

我们将会定位到
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#configure()

private void configure() throws Exception {
    【1】获取配置对象
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

    for (SecurityConfigurer<O, B> configurer : configurers) {
      【2】将配置对象加入到过滤器链
       configurer.configure((B) this);
    }
}

在【1】处,我们将得到从securityFilterChainBuilder中得到configurers配置列表

image.png

在【2】处,会循环配置列表对每个配置对象处理并且最终加入到过滤器链中。
比如当前是ExceptionHandlingConfigurer
那么就会执行
org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer#configure()

@Override
public void configure(H http) {
    AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
    // 【1】创建 ExceptionTranslationFilter
    ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
          entryPoint, getRequestCache(http));
    AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
    exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
    exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
    
    //【2】 ExceptionTranslationFilter加入到过滤器链
    http.addFilter(exceptionTranslationFilter);
}

再比如当前是HeaderConfigurer,那么会执行org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#configure()

@Override
public void configure(H http) {
    【1】创建 HeaderWriterFilter
    HeaderWriterFilter headersFilter = createHeaderWriterFilter();
    
    【2】加入到过滤器链
    http.addFilter(headersFilter);
}

也就是说,具体是什么类型的Filter,是根据当前Configurer而决定的

当我们执行完所有的configure()方法,再次回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()

可以看到filter已经被加载出来

image.png

但是,过滤器是有一定顺序的,此时加载出来的过滤器并没有处理顺序。
当方法执行到 performBuild()内部时,会进行排序
org.springframework.security.config.annotation.web.builders.HttpSecurity#performBuild()

@Override
protected DefaultSecurityFilterChain performBuild() {
    【对过滤器进行排序】
    filters.sort(comparator);
    return new DefaultSecurityFilterChain(requestMatcher, filters);
}

排序后的过滤器链:

image.png

到这里,拦截器链加载结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1673436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode算法题:49. 字母异位词分组(Java)

给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate", "nat", …

试试这四个AI论文工具和降重技术,低成本高回报

在科研领域&#xff0c;AI写作工具如同新一代的科研利器&#xff0c;它们能够极大提高文献查阅、思路整理和表达优化的效率&#xff0c;本质上促进了科研工作的进步。AI写作工具不仅快速获取并整理海量信息&#xff0c;还帮助我们精确提炼中心思想&#xff0c;显著提升论文写作…

Windows系统安装MongoDB数据库

MongoDB是一个基于分布式文件存储的NoSQL数据库&#xff0c;由C语言编写的。MongoDB的数据存储基本单元是文档&#xff0c;它是由多个键值对有序组合的数据单元&#xff0c;类似于关系数据库中的数据记录。适合存储JSON形式的数据&#xff0c;数据格式自由&#xff0c;不固定。…

自制无感无刷电机驱动板

自制无感无刷电机驱动板 分别测试了基于C251的STC32G单片机、Arduino AVR的ATmega328PB、以及ARM的ST32F103单片机。 &#x1f9f2;测试转动效果 ✒目前市面上开源的有关无刷电机的项目数不胜数&#xff0c;其控制原理都大同小异&#xff0c;在没有领透其技术要领情况下&#x…

怎么申请一年期免费的https证书

随着互联网的推广和普及&#xff0c;如今HTTPS证书的普及度还是比较高的了&#xff0c;大家对于https证书的需求度也在日益提升。针对于一些个人用户或是企业而言&#xff0c;实现网站的https访问已经成为了一种标配。从去年年底开始&#xff0c;各大SSL证书厂商陆续下架一年期…

用Python和GUI实现Socket多线程通信方案

下面是一个使用 Python 和 Tkinter GUI 库实现 Socket 多线程通信的简单示例。在这个示例中&#xff0c;我是创建了一个简单的聊天应用&#xff0c;其中服务器和客户端可以通过 Socket 进行通信。 1、问题背景 这个问题与在 Python 应用中使用 pyGTK、线程和套接字相关。开发者…

GD32驱动LCD12864

目录 1、引言 1.1、LCD12864基本概念和作用。 1.2、硬件引脚 2、GD32微控制器简介 3、LCD12864显示屏简介 3.1、模块引脚说明 3.2、模块连接方式 4、驱动原理 4.1、指令集 4.2、显示坐标关系 5、软件开发 6、硬件连接 7、效果演示 8、附录 1、引言 1.1、LCD12…

地下车库导航地图怎么做?停车场地图绘制软件哪个好?

上海懒图科技以先进技术和丰富的行业服务经验为用户提供停车场景下的全流程服务平台&#xff0c;用户基于平台可自主快速绘制酷炫的停车场地图&#xff0c;通过提供完善的停车场应用功能集和扩展API服务包&#xff0c;可以方便地实现电子地图服务于您的各类停车场应用中&#x…

【SRC实战】小游戏漏洞修改分数打榜

挖个洞先 https://mp.weixin.qq.com/s/Um0HU2srvZ0UlZRAsbSVug “ 以下漏洞均为实验靶场&#xff0c;如有雷同&#xff0c;纯属巧合 ” 01 — 漏洞证明 “ 如何刷分提高排名&#xff1f;” 1、进入小游戏&#xff0c;类似于跳一跳 2、开始时每次加1分 3、随着游戏进行…

中国GDP空间分布数据集

中国GDP空间分布公里网格数据集是在全国分县GDP统计数据的基础上&#xff0c;考虑人类活动密切相关的土地利用类型、夜间灯光亮度、居民点密度数据与GDP的空间互动规律&#xff0c;通过空间插值生成的空间格网数据。数据包括1995、2000、2005、2010、2015和2019年6期。该数据集…

Vue3实战笔记(20)—封装头部导航组件

文章目录 前言一、封装头部导航栏二、使用步骤总结 前言 Vue 3 封装头部导航栏有助于提高代码复用性、统一风格、降低维护成本、提高可配置性和模块化程度&#xff0c;同时还可以实现动态渲染等功能&#xff0c;有利于项目开发和维护。 一、封装头部导航栏 封装头部导航栏&am…

11个免费的 android数据恢复应用程序功能分析

在手机上丢失数据是一个很大的错误。但是&#xff0c;在这种情况下&#xff0c;除了惊慌失措之外&#xff0c;最好开始使用android数据恢复应用程序搜索以查找将其取回的方法。您可以检查手机的备份存储以在Android上进行数据恢复&#xff0c;但是如果数据仍然无处可寻&#xf…

spring cloud alibaba、spring cloud和springboot三者的版本兼容

官方版本说明地址: 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 组件版本关系 每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示(注意,Spring Cloud Dubbo 从 2021.0.1.0 起已被移除出主干,不再随主干演进): Spring Cloud Alibaba Ve…

如何利用AI生成答辩PPT?笔灵AI答辩PPT,智能识别关键点

很多快要毕业的同学在做答辩PPT的时候总是感觉毫无思路&#xff0c;一窍不通。但这并不是你们的错&#xff0c;对于平时没接触过相关方面&#xff0c;第一次搞答辩PPT的人来说&#xff0c;这是很正常的一件事。一个好的答辩PPT可以根据以下分为以下几部分来写。 1.研究的背景和…

业绩超预期外,海尔智家ESG实践再获认可

求增长&#xff0c;已成各行业面对的共同命题。 在家电赛道上&#xff0c;海尔智家的业绩表现不俗。2024一季度&#xff0c;海尔智家延续了年报稳健增长的趋势&#xff0c;继续在高基数下实现高增长&#xff0c;其利润增幅更是高达20.2&#xff05;&#xff0c;超预期。而今年…

图像质量评价指标:了解图像质量的度量方式

图像质量评价指标&#xff1a;了解图像质量的度量方式 在图像处理和计算机视觉领域&#xff0c;评价图像质量的准确性对于许多应用至关重要。通过合适的评价指标&#xff0c;我们可以量化图像的质量&#xff0c;从而更好地了解图像处理算法的效果和改进空间。本文将介绍图像质…

File类~路径、创建文件对象

路径分为相对路径&#xff08;不带盘符&#xff09;&#xff0c;绝对路径&#xff08;带盘符&#xff09; 路径是可以存在的&#xff0c;也可以是不存在的 创建文件对象的三个方法&#xff1a;

如何管理Linux环境变量?

1、查看环境变量 Linux 发行版具有环境变量和 shell 变量&#xff0c;它们的功能类似&#xff0c;但作用域不同。 要列出 Linux 中的所有环境变量&#xff0c;运行printenv命令。 在流行的 Linux 发行版中&#xff0c;一些最常见的环境变量包括&#xff1a; PWD – 当前工作目录…

上位机图像处理和嵌入式模块部署(树莓派4b的替代品)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 实话实说&#xff0c;树莓派4b的产品力还是比较优秀的&#xff0c;价格还算适中。但是和国产卡片电脑比起来&#xff0c;则逊色不少。功能差不多的…

BGP—边界网关协议

BGP 动态路由协议可以按照工作范围分为IGP以及EGP。IGP工作在同一个AS内&#xff0c;主要用来发现和计算路由&#xff0c;为AS内提供路由信息的交换&#xff1b;而EGP工作在AS与AS之间&#xff0c;在AS间提供无环路的路由信息交换&#xff0c;BGP则是EGP的一种。 BGP是一…