详解Spring Security

news2025/1/12 23:03:44

目录

1.概述

2.登录

2.1.默认用户

2.2.自定义用户

2.3.加密

2.4.绕过加密

2.5.怎么传递用户信息

2.6.记住我

3.登出

4.使用数据库

4.1.jdbcAuthentication

4.2.userDetailsService

5.自定义处理器

6.更多细粒度的控制

7.原理简述


1.概述

Spring Security是一个基于Spring框架的安全性框架,它提供了一系列的API和扩展点,可以帮助开发人员在应用程序中轻松地实现安全认证和授权控制。

我们可以理解为Spring Security维护了一组我们可以自定义的访问规则,每次访问都会去进行规则比对,满足规则的才放行。

这些规则可以有很多维度,本文会以最基础的基于角色的控制入手逐步扩展详细介绍Spring Security。

基于角色的控制,我们可以理解为Spring Security为我们维护了一张“白名单”,而登录就是去白名单里进行比对。

2.登录

2.1.默认用户

依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

依赖引入后其实spring security就已经生效了,此时访问我们的接口会自动转跳spring security内置的登录页,验证通过后才会转跳到后端接口:

默认用户名:user

默认密码:会在日志中输出

2.2.自定义用户

spring security给我们提供了接口来配置security,其中就包括自定义用户和角色:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().
                //所有用户都可以访问/all
                        antMatchers("/all").permitAll().
                //admin可以访问/admin
                        antMatchers("/admin").hasRole("admin");
        //如果验证未通过,转跳spring security自带的登录页面进行登录
        //如果不配置此处的步骤,验证未通过则会直接返回403访问被拒绝。
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //自定义两个用户user、admin,user对应user角色,admin对应admin角色
        auth.inMemoryAuthentication()
                .withUser("user").password("123").roles("user")
                .and()
                .withUser("admin").password("456").roles("user", "admin");
    }
}

2.3.加密

完成上面的配置这时候我们再访问我们的接口,转跳登录页后输入我们定义的有对应访问用户名密码,比如admin 456,似乎就应该能正常访问到我们的接口了。但是实际上会报错:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

这是因为我们在定义用户的时候密码没有加密,自spring security 5.0开始就要求必须对密码进行加密,否则,在进行密码比较时,就会出现无法解析密码加密算法的异常。我们当前用的主流版本一定是5.0以上,所以加密是必须的。

spring security提供了一个PasswordEncoder接口给我们自定器,在加密器里可以通过方法重写来自定义加密过程。但是实际使用上不用费力去自己写一个加密算法,security给我们准备了多种加密器、多种加密方法,而且自己写的也肯定没有开源的稳定和好用。

spring security提供的加密器如下:

  • BCryptPasswordEncoder:这是最常用的加密算法之一,它使用哈希和随机盐来加密密码。

  • Pbkdf2PasswordEncoder:这也是一种密码加密算法,它使用基于密码的密钥导出函数(PBKDF2)来加密密码。

  • SCryptPasswordEncoder:这是一种基于内存的密码哈希算法,它使用大量的内存来防止散列碰撞攻击。

  • NoOpPasswordEncoder:这是一种不安全的加密算法,它仅仅是将明文密码作为加密后的密码。不建议在生产环境中使用。

我们就挑选BCryptPasswordEncoder来对密码进行加密,修改一下,在定义用户名密码的时候用BCryptPasswordEncoder来进行加密:

 @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //自定义两个用户user、admin,user对应user角色,admin对应admin角色
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password("123").roles("user")
                .and()
                .withUser("admin").password("456").roles("user", "admin");
    }

加密之后我们再在登陆页面进行登录的时候就不能用明文的admin 456来进行登录了,需要将456使用同样的加密算法加密后的密文密码来登录。

2.4.绕过加密

在实际生产环境种使用密文密码当然是无可厚非的,但是在开发、测试阶段会很麻烦,有没有绕过的办法喃?有的,{noop}","no operation"的缩写,表示不执行任何操作。如果密码的前缀是"{noop}",则Spring Security会将其识别为明文密码,不进行加密,直接存储到数据库或内存中。可以将上面的代码改成:

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //自定义两个用户user、admin,user对应user角色,admin对应admin角色
        auth.inMemoryAuthentication()
                .withUser("user").password("{noop}123").roles("user")
                .and()
                .withUser("admin").password("{noop}456").roles("user", "admin");
    }

2.5.怎么传递用户信息

由于我们的请求我们都是在登陆界面输入用户名密码,实际使用中不可能每次都这样去做,怎么把用户名、密码携带在请求里传给security喃?这却决于配置为哪种,支持两种方式:

  • 表单

  • 放在请求头中

表单:

后端配置:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
                //指定用表单的方式登录
                .formLogin()
                //登录地址,不配置的话会有默认值,默认是login,可以用这个配置来设置新的请求页
                .loginPage("/login")
                //配置用户名的参数名,不配置的话会有默认值,默认是username
                .usernameParameter("username")
                //配置密码的参数名,不配置的话会有默认值,默认是password
                .passwordParameter("password")
                .permitAll()
    }

前端表单:

<form action="/login" method="post">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username"><br><br>
  <label for="password">Password:</label>
  <input type="password" id="password" name="password"><br><br>
  <input type="submit" value="Submit">
</form>

当用户提交表单后,会向后端发送一个POST请求,请求的URL为/login,请求参数包含用户名和密码。

放在表头中:

在请求头中添加Authorization字段,将用户名和密码进行Base64编码后传递给后端进行验证。后端配置示例:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/", "/home").permitAll()
        .anyRequest().authenticated()
        .and()
        .httpBasic()
        .permitAll();
}

2.6.记住我

security支持“记住我”这个功能,是基于token来实现得,开启记住我后,登陆成功后会返给客户端一个cookie,用这个cookie来实现记住我的效果,配置示例如下:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().
                antMatchers("/all").permitAll().
                antMatchers("/admin").hasRole("admin").and()
                .formLogin().and()
                //开启记住我功能
                .rememberMe()
                //设置返回的cookie的键名
                .rememberMeParameter("remember-me")
                //设置过期时间
                .tokenValiditySeconds(7*24*60*60);
    }

3.登出

讲完登陆后再看登出就很好明白:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // 其他配置
        .logout()
            .logoutUrl("/logout") // 配置登出的URL
            .logoutSuccessUrl("/login") // 配置登出成功后的跳转URL
            .deleteCookies("remember-me") // 删除Cookie
            .permitAll(); // 允许所有用户访问登出URL
}

4.使用数据库

上文中我们都是用auth.inMemoryAuthentication()将定义的用户和角色信息放在内存中,在实际业务场景中有时候我们是需要将这些信息放在数据库中进行持久化的。spring security中有两种常用方式来将信息放在数据库中:

  • auth.jdbcAuthentication()

  • auth.userDetailsService()

4.1.jdbcAuthentication

使用这种方式时,严格规定了表结构,具体的建表语句在这里:

建出来后会有两个表:

这里要注意几点:

  • 权限前缀要加ROLE_,不然不会生效,enabled属性是用户是否生效1是0否

  • 密码一定要使用加密,不加密识别不了

接下来就是使用:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username, password, enabled from users where username=?")
            .authoritiesByUsernameQuery("select username, authority from authorities where username=?")
            .passwordEncoder(new BCryptPasswordEncoder());
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
    }
}

4.2.userDetailsService

jdbcAuthentication这种方式中对表结构有严格要求,如果觉得太死,需要灵活一点,security还提供了userDetailsService这种方式灵活的来让我们灵活的使用。

我们可以自己实现UserDetailsService,重新loadUserByUsername方法,在方法中灵活的去将系统用户转为security的用户。

@Service
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //从存储中获取系统用户
        SystemUser systemUser = userDao.getUser();
        //将系统用户的用户名密码来创建security的用户
        User.UserBuilder builder = User.builder();
        UserDetails user = builder.username(systemUser.getUserName()).password(systemUser.getPassword()).roles("admin").build();
        return user;
    }
}

5.自定义处理器

在Spring Security中,默认认证成功了就能访问到接口,但是如果我想认证成功后不直接访问接口而是做其他事情喃?spring security也提供了我们自定义操作完成后的处理过程的能力,我们可以使用自定义的处理器(Handler)来处理认证、授权、注销等操作。自定义处理器可以通过实现相应的接口或继承相应的类来完成。

以下是几个常用的自定义处理器的接口/类及其作用:

  1. AuthenticationSuccessHandler:用于认证成功后的处理,比如记录日志、跳转到指定页面等。

  2. AuthenticationFailureHandler:用于认证失败后的处理,比如记录日志、显示错误信息等。

  3. AccessDeniedHandler:用于处理访问被拒绝的情况,比如跳转到错误页面、记录日志等。

  4. LogoutSuccessHandler:用于注销成功后的处理,比如跳转到登录页面、删除Cookie等。

  5. InvalidSessionStrategy:用于处理无效的Session,比如跳转到登录页面、记录日志等。

在Spring Security中,我们可以通过配置来使用自定义的处理器。例如:

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 处理认证成功的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/index");
    }
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 处理认证失败的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/login?error=true");
    }
}
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        // 处理访问拒绝的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/accessDenied");
    }
}
public class MyLogoutHandler implements LogoutHandler {

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 处理注销的逻辑,比如记录日志、清除缓存等
    }
}
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 处理注销成功的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/logoutSuccess");
    }
}

定义完后直接使用即可:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    //省略其他配置...
    
    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
    
    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    
    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    
    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    
    @Autowired
    private CustomInvalidSessionStrategy customInvalidSessionStrategy;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .successHandler(customAuthenticationSuccessHandler)
            .failureHandler(customAuthenticationFailureHandler)
            .and()
            .exceptionHandling()
            .accessDeniedHandler(customAccessDeniedHandler)
            .and()
            .logout()
            .logoutSuccessHandler(customLogoutSuccessHandler)
            .and()
            .sessionManagement()
            .invalidSessionStrategy(customInvalidSessionStrategy);
    }
    
}

6.更多细粒度的控制

前文我们都是基于角色来进行访问控制的,稍加思考就会想到,光是靠角色来控制实际应用中可能不够,有时候还要基于HTTP METHOD、IP、权限等等,spring security当然给我们提供了这些更加细粒度的访问控制方法:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 配置安全规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        
            // 基于角色控制访问
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            
            // 基于权限控制访问
            .antMatchers("/article/**").hasAuthority("ARTICLE_VIEW")
            
            // 基于IP地址控制访问
            .antMatchers("/profile/**").hasIpAddress("192.168.1.0/24")
            
            // 基于方法表达式控制访问
            .antMatchers(HttpMethod.DELETE, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_DELETE')")
            .antMatchers(HttpMethod.PUT, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_EDIT')")
            
            // 其他请求需要进行身份认证
            .anyRequest().authenticated()
            
            .and()
            .formLogin()
            
            .and()
            .httpBasic();
    }

    // 配置用户信息
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
        
            // 配置用户角色和权限
            .withUser("user").password("{noop}password").roles("USER").authorities("ARTICLE_VIEW")
            .and()
            .withUser("admin").password("{noop}password").roles("ADMIN").authorities("ARTICLE_VIEW", "ARTICLE_EDIT", "ARTICLE_DELETE");
    }
}

当然除了代码里可以配置,也提供了注解版本:

// 基于角色的访问控制注解
    @Secured("ROLE_ADMIN")
    public void adminOnlyMethod() {
        // 只有 ADMIN 角色的用户可以调用这个方法
    }
 
    // 基于角色的访问控制注解
    @RolesAllowed("ROLE_USER")
    public void userOnlyMethod() {
        // 只有 USER 角色的用户可以调用这个方法
    }
 
    // 基于方法调用之后的返回值进行访问控制
    @PostAuthorize("returnObject.owner == authentication.name")
    public Resource getResourceById(String id) {
        // 获取资源
        return resource;
    }
 
    // 在方法调用前进行集合过滤
    @PreFilter("filterObject.owner == authentication.name")
    public List<Resource> getResources(List<String> ids) {
        // 获取指定 ID 的资源列表
        return resources;
    }
 
    // 在方法调用后进行集合过滤
    @PostFilter("filterObject.owner == authentication.name")
    public List<Resource> getAllResources() {
        // 获取所有资源列表
        return resources;
    }
 
    // 基于SpEL表达式的访问控制注解,控制HTTP方法
    @PreAuthorize("hasRole('ADMIN') and #httpMethod == 'GET'")
    @GetMapping("/adminOnly")
    public ResponseEntity<String> adminOnlyEndpoint(HttpServletRequest request) {
        return ResponseEntity.ok("Only accessible to admins");
    }

7.原理简述

看上图,Spring Security的实现原理一目了然,其实它就是通过一个Servlet过滤器来挂载到应用程序的请求流程中。当一个请求到达应用程序时,它首先会被Spring Security的过滤器拦截,进行安全控制处理,然后将请求继续传递给应用程序的其他组件进行处理。

这里面的投票器不用深究,它的作用其实就是用于决定一个用户是否有访问特定资源的权限。投票器通过对安全上下文(SecurityContext)中的身份验证信息、访问控制列表(ACL)和其他因素进行评估,决定是否允许用户访问资源。

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

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

相关文章

使用 Kubernetes 运行 non-root .NET 容器

翻译自 Richard Lander 的博客 Rootless 或 non-root Linux 容器一直是 .NET 容器团队最需要的功能。我们最近宣布了所有 .NET 8 容器镜像都可以通过一行代码配置为 non-root 用户。今天的文章将介绍如何使用 Kubernetes 处理 non-root 托管。 您可以尝试使用我们的 non-root…

gateway报 netty堆外内存溢出问题解决io.netty.util.internal.OutOfDirectMemoryError

昨天线上网关突然无法访问。打开日志看到错误信息“io.netty.util.internal.OutOfDirectMemoryError” 堆外内存溢出。。这也没碰到过啊&#xff0c;看来今天准点下班的愿望又落空了。老规矩面向百度编程。先看看网上有没有其他兄弟碰到这个问题。一顿搜索之后发现&#xff0c;…

已解决windows pycocotools安装失败问题 —— 超简单

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,YOLO,活动领域博主爱笑的男孩。擅长深度学习,YOLO,活动,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typecollect个人…

分库分表与分布式主键生成策略详解--一个无数人踩过却一直被人忽视的深坑

文章目录 一、从分库分表的一个神坑说起二、分布式主键要考虑哪些问题&#xff1f;三、主要的主键生成策略1、数据库策略2、应用单独生成3、第三方服务统一生成4、与第三方结合的segment策略 四、定制雪花算法1、如影随形的时钟回拨问题2、用主键生成策略优化分配工作进程位3、…

萤石“小步快走”,跨进智能家居生态圈

文丨智能相对论 作者丨Kinki 近日&#xff0c;萤石网络&#xff08;下称“萤石”&#xff09;举办了2023春季新品发布会&#xff0c;这是公司上市以来的首个新品发布会&#xff0c;除了拳头产品智能家居摄像机之外&#xff0c;还有智能入户产品、TV Studio等十多款新品&#…

NC65 集团业务参数 GLS01参数值的默认值作用是什么?

NC65 集团业务参数 GLS01参数值的默认值作用是什么&#xff1f; 用在总账系统的所有账簿。如果设置的期间个数大于12&#xff0c;还得要求所查询的账表支持跨年查询&#xff0c;比如科目余额表&#xff0c;不支持跨年&#xff08;注意&#xff1a;这里说的不支持跨年是指余额为…

4.2.1朴素模式匹配算法

什么是字符串的模式匹配&#xff1a; 从这段字符串里面搜索内容&#xff0c;被搜索的字符串我们称之为主串。 也可能匹配不到 主串长度为n&#xff0c;模式串长度为m。 朴素模式匹配算法&#xff1a;将主串中所有长度为m的字串依次与模式串对比&#xff0c;直到找到一个完全匹…

【JavaEE】File、InputStream和OutputStream的使用

1.File 在计算机中目录结构如下&#xff1a; 而File就表示一个目录或者一个普通文件。 File表示目录&#xff1a; File表示普通文件&#xff1a; 我们先来看File的构造方法&#xff1a; 构造器描述File(File parent, String child)根据父目录 孩子文件路径&#xff0c;创…

Linux权限提升—定时任务、环境变量、权限配置不当、数据库等提权

Linux权限提升—定时任务、环境变量、权限配置不当、数据库等提权 1. 前言1.1. 如何找编译好的EXP 2. 定时任务提权2.1. 查看定时任务2.2. 通配符注入提权2.2.1. 创建执行脚本2.2.2. 创建定时任务2.2.3. 查看效果2.2.4. 提权操作2.2.4.1. 切换普通用户2.2.4.2. 执行命令2.2.4.3…

优先、双端队列-我的基础算法刷题之路(八)

本篇博客旨在整理记录自已对优先队列、双端队列的一些总结&#xff0c;以及刷题的解题思路&#xff0c;同时希望可给小伙伴一些帮助。本人也是算法小白&#xff0c;水平有限&#xff0c;如果文章中有什么错误之处&#xff0c;希望小伙伴们可以在评论区指出来&#xff0c;共勉 &…

Netty 源码解析(下)

接上一篇博客 Netty 源码解析&#xff08;上&#xff09;继续分析 上一篇博客中已经分析了绝大部分 ChannelFuture cf bootstrap.bind(9000).sync(); 这一行代码&#xff0c;当这一行代码运行完时&#xff0c;Netty服务端就已经启动好了&#xff0c;接下来就是接收链接&#x…

Spring Security OAuth2.0(二)-----简化模式/密码模式/客户端模式/刷新 token

简化模式 代码示例 修改authorization_server授权服务模块 新增“implicit” 和修改回调地址为本次地址 修改第三方应用项目搭建新页面模拟 新建implicit.jsp <% page contentType"text/html;charsetUTF-8" language"java" isELIgnored"fals…

C++入门篇(二)

目录 一、引用1.1 什么是引用&#xff1f;1.2 引用的特性1.3 常引用1.4 引用的使用场景1.5 传值和传引用效率比较1.5.1 传值和传引用做参数的性能对比1.5.2 传值和传引用做返回值的性能对比 1.6 引用和指针之间的区别 二、内联函数2.1 什么是内联函数&#xff1f;2.2 内联函数的…

知识变现海哥|研究了100个项目,这个才是人生逆袭首选

&#xff08;本文源自公号跟海哥学知识变现&#xff0c;移步公号与100万知识变现/知识付费创业者&#xff0c;一起学知识变现知识付费干货&#xff0c;回‘领书’获取3本电子书&#xff1a;【知识付费秘籍】【知识创业者成长手册】【100个知识付费成功案例】) 经常有人问海哥&a…

什么是中断向量表?作用是什么?为什么需要偏移?

一、定义与特点 定义&#xff1a;中断向量表(interrupt vector table)包含中断服务程序地址的特定内存区域&#xff0c;这些服务程序是处理外部硬件中断请求的代码。 特点&#xff1a;这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的&#xff0c;当某个中断…

ESP32-C2开发板 Homekit烧录教程

准备 1.1硬件ESP32 C2开发板&#xff0c;如图1-1所示 图1-1 ESP32 C2开发板 1.2软件 CozyLife APP可以在各大应用市场搜索下载&#xff0c;也可以扫描二维码下载如图1-2所示 HomeKit flash download tool 烧录工具 esp32c2 homkit演示固件 烧录教程 打开flash_download_to…

以智求治,MapGIS打造公共安全治理新模式

随着我国城市人口和规模日益扩大&#xff0c;城市运行系统日趋复杂&#xff0c;各类风险隐患增多且呈现相互叠加、相互耦合态势&#xff0c;各类风险、事故灾害类事件造成的损失严重&#xff0c;公共安全防范压力不断增大。 党的二十大报告中指出&#xff1a;“建立大安全大应…

【容器化应用程序设计和开发】2.1 容器化基础知识和Docker容器

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 容器化应用程序设计和开发 2.1 容器化基础知识和Docker容器2.1.1 容器的创建2.1.2 容器的管理 容器化应用程序设计和开发是一种基于容器技术的应用程序设计和开发方法论。它将应用程序拆分为多个小型服务&#x…

Centos7安装MySQL-5.7.17详细教程

1、MySQL安装包可以在Oracle官网下载 下载链接:https://dev.mysql.com/downloads/mysql/5.7.html 也可以通过wget命令下载 wget http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.17-linux-glibc2.5-x86_64.tar.gz 下载完成之后解压tar包 命令&#xff1a;tar -zxvf…

Smith预估控制器

Smith预估器主要针对存在大滞后的系统,作用延迟和反馈延迟环节的控制,Smith预估器的另一篇文章,请参看下面的博客文章: 博途1200/1500PLC Smith预估器(补偿器)算法实现(FB)_RXXW_Dor的博客-CSDN博客在写这篇文章之前写过一篇"大林控制算法",大家可以参看下面这…