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

Spring Boot | Spring Security ( SpringBoot安全管理 )、Spring Security中 的 “自定义用户认证“

目录 :

  • Spring Boot 安全管理 :
    • 一、Spring Security 介绍
    • 二、Spring Security 快速入门
      • 2.1 基础环境搭建 :
        • ① 创建Spring Boot 项目
        • ② 创建 html资源文件
        • ③ 编写Web控制层
      • 2.2 开启安全管理效果测试 :
        • ④ 添加 spring-boot-starter-security 启动器
        • ⑤ 项目启动测试
    • 三、"MVC Security" 安全配置介绍
    • 四、自定义 "用户认证" ( 确认用户"是否登录" , 并对登录进行 "管控" )
      • 4.1 "内存" 身份认证 ( In-Memory Authentication ) - ( 开发测试环境中使用,无法用于"实际生产环境" ) :
        • ① 自定义 WebSecurityConfigurerAdapter 配置类 ( 创建一个配置类,该类继承 WebSecurityConfigurerAdapter类 )
        • ② 使用 "内存" 进行 "身份认证"
        • ③ 效果测试
      • 4.2 "JDBC" 身份认证 ( JDBC Authentication ) - ( 将"用户登录信息" 与 "数据库信息" 进行 比对,判断是否是"合法用户" ) :
        • ① 数据准备
        • ② 添加 "JDBC 连接数据库" 的 "依赖启动器"
        • ③ application.properties 中 进行 "数据库连接配置"
        • ④ 使用 JDBC 进行 "身份认证"
        • ⑤ 效果测试
      • 4.3 "UserDetailsService" 身份认证 :
        • ① 基础项目文件准备
        • ② 定义查询用户及角色信息的服务接口
        • ③ 定义 "UserDetailsService" 用于封装认证用户信息 ( 创建类实现 "UserDetailsService接口" , 在该类中封装 "认证用户的信息" )
        • ④ 使用 UserDetailsService 进行身份认证 ( 创建一个配置类,该类继承 WebSecurityConfigurerAdapter类 , 类中重写的方法中调用 userDetailsService( )实现 "UserDetailsService身份认证" )
        • ⑤ 效果测试

Spring Boot 安全管理 :

在这里插入图片描述

作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!

该文章参考学习教材为:
《Spring Boot企业级开发教程》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章

文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!

(侵权可联系我,进行删除,如果雷同,纯属巧合)


  • 实际开发中一些应用通常要考虑安全性问题。例如,对于一些重要的操作有些请求需要用户验明身份可以执行,还有一些请求需要用户 具有特定权限可以执行。这样做的意义不仅可以用来保护项目安全,还可以控制项目访问效果

一、Spring Security 介绍

  • 针对 项目的安全管理Spring家族提供了安全框架 : Spring Security,它是一个 基于 Spring生态圈的,用于提供安全访问控制解决方案的框架,为了方便 Spring Boot 项目安全管理Spring BootSpring Security 安全框架进行了 整合支持,并提供了 通用自动化配置,从而实现了 Spring Security 安全框架中包含的多数安全管理功能,下面,针对 常见的安全管理功能进行介绍,具体如下

    (1) MVC SecuritySpring Boot 整合 Spring MVC 搭建 Web 应用安全管理框架,也是 开发中 "使用最多一款安全功能

    (2) WebFlux SecuritySpring Boot 整合 Spring WebFlux 搭建 Web 应用安全管理。虽然 Spring WebFlux 框架刚出现不久、文档不够健全,但是它集成了其他安全功能的优点,后续有可能在 Web 开发中越来越流行。

    (3) OAuth2大型项目安全管理框架,可以实现 第三方认证单点登录 等功能,但是目前 Spring Boot 版本
    还不支持 OAuth2 安全管理框架。

    (4) Actuator Security用于对项目的一些 运行环境提供安全监控,例如 Health 健康信息、Info 运行信息等,它主要作为系统指标供运维人员查看管理系统的运行情况。


    上面介绍Spring Boot 整合 Spring Security 安全框架可以实现的一些安全管理功能项目安全管理是一个很大的话题,开发者可以 根据实际项目需求选择性地 使用 Spring Security 安全框架 中的功能

二、Spring Security 快速入门

  • Spring Security安全管理两个重要概念,分别是 Authentication ( 认证 )和 Authorization ( 授权 )。其中,
    认证确认用户是否登录,并对 用户登录 进行 管控
    授权确定用户所拥有功能权限,并对 用户权限进行管控
  • 本章后续将对 Spring Boot 整合 Spring Security 进行安全管理的讲解和实现将围绕用户登录管理访问权限控制进行。下面我们先通过一个 快速入门案例 来体验 Spring Boot 整合 Spring Security 实现的 MVC Security 安全管理效果

2.1 基础环境搭建 :

  • 为了更好地使用 Spring Boot 整合实现 MVC Security 安全管理功能,实现 Authentication( 认证 )Authorization
    ( 授权 ) 的功能,下面通过一个案例进行演示讲解
① 创建Spring Boot 项目
  • 创建项目 :
    在这里插入图片描述


    项目结构

    在这里插入图片描述

② 创建 html资源文件
  • 创建 html资源文件 :
    index.html页面是项目首页页面commonvip文件夹中分别对应普通用户VIP用户可访问的页面

    index.html :

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf模板引擎页面配置 -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>影视直播厅</title>
    </head>
    <body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
    <h1 align="center">欢迎进入电影网站首页</h1>
    <hr>
    <h3>普通电影</h3>
    <ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
    </ul>
    <h3>VIP专享</h3>
    <ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/1}">猩球崛起</a></li>
    </ul>
    </body>
    </html>
    

    1.html : ( 其他三个页面以此类推 )

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>Title</title>
    </head>
    <body>
    <!-- th:href="@{/}" : 返回项目首页-->
    <a th:href="@{/}">返回</a>
    <h1>飞驰人生</h1>
    .....
    </body>
    </html>
    
③ 编写Web控制层
  • FileController.java :

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;@Controller //加入到IOC容器
    public class FileController { //控制层类//影片详情页@GetMapping("/detail/{type}/{path}") //为路径变量public String toDetail(@PathVariable("type") String type, @PathVariable("path") String path) {//返回值为String类型,可用于返回视图页面return "detail/" + type + "/" + path;}}
    

2.2 开启安全管理效果测试 :

④ 添加 spring-boot-starter-security 启动器
  • Spring Boot 项目开启 Spring Security方式非常简单,只需要引入 spring-boot-starter-security 启动器即可。

    <!-- Spring Security 的依赖启动器,其版本号由SpringBoot进行统一管理-->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    需要说明的是,一旦项目 引入 spring-boot-starter-security 启动器MVC SecurityWebFlux Security 负责的 安全功能都会立即生效

    ps :
    WebFlux Security 生效另一个前提是项目 属于 WebFlux Web 项目 );
    ② 对于 OAuth2 安全管理功能来说,则还
    需要额外引入
    一些其他安全依赖

⑤ 项目启动测试
  • 启动项目后,仔细 查看控制台打印信息项目启动时会在 "控制台"上 自动生成一个“安全密码” ( security password ) , 这个
    密码每次项目启动时 "随机生成" 的 ,如下图所示 :

    在这里插入图片描述

  • 此时通过浏览器访问 http://localhost:8080/ 访问 项目首页,会自动跳转到一个新的登录链接页面 : http://localhost:8080/login , 这说明在项目添加 spring-boot-starter-security依赖启动器后,项目实现Spring Security自动化配置,并且 具有了一些默认安全管理功能。另外,项目中并没有手动创建用户登录页面,而添加了Security 依赖后Spring Security自带一个默认登录页面如下图所示 :

    在这里插入图片描述

  • 登录页面随意输入一个错误用户名密码,会出现 错误提示,效果如下图所示 :

    在这里插入图片描述

    上图可以看出,当在 Spring Security 提供的默认登录页面“/ogin”中输入错误登录信息后,会 重定向到“/ogin?error页面并显示出错误信息“用户名或密码错误”。

    需要说明的是,在 Spring Boot 项目中加入 spring-boot-starter-security 依赖启动器Security 会默认提供一个可登录的用户信息,其中 用户名user密码随机生成,这个密码随着项目每次启动随机生成打印在控制台 上。

  • 输入账号密码 后,会 自动跳转 到 “登录首页” :

    在这里插入图片描述

    注意点 :

    这种默认安全管理方式存在诸多问题例如,只有唯一的默认登录用户 :user密码随机生成过于暴露登录页面错误提示页面不是我们想要的等

三、“MVC Security” 安全配置介绍

  • 使用 Spring BootSpring MVC 进行 Web 开发时,如果项目引入 spring-boot-starter-security 依赖启动器MVC Security 安全管理功能就会自动生效,其 默认安全配置 是在 SecurityAutoConfiguration
    UserDetailsServiceAutoConfiguration实现的

  • 其中, SecurityAutoConfiguration导入并自动化配置 : SpringBootWebSecurityConfiguration用于启动 Web安全管理 UserDetailsServiceAutoConfiguration 则用于 配置用户身份信息

  • 通过自定义 WebSecurityConfigurerAdapter 类型的 Bean 组件,可以 完全关闭 Security 提供的 Web 应用默认安全配置,但是 不会关闭 UserDetailsService 用户信息自动配置类如果要关闭 UserDetailsService默认用户信息配置,可以自定义UserDetailsServiceAuthenticationProviderAuthenticationManager 类型的 Bean 组件

    另外,可以通过自定义 WebSecurityConfigurerAdapter 类型的 Bean 组件 覆盖默认访问规则。Spring Boot 提供了非常多方便的方法,可用于覆盖请求映射静态资源的访问规则

  • 下面我们通过 Spring Security API查看 WebSecurityConfigurerAdapter主要方法,具体如下表所示

    方法描述
    configure ( AuthenticationManagerBuilder auth )定制用户认证管理器来实现 用户认证 ( 确认用户"是否登录" ,并对登录进行 “管控” )
    configure ( HttpSecurity http )定制基于 HTTP 请求用户访问控制 ( 权限管理 / 授权管理 )

四、自定义 “用户认证” ( 确认用户"是否登录" , 并对登录进行 “管控” )

  • 通过 自定义 WebSecurityConfigurerAdapter 类型Bean 组件重写 confiqure ( AuthenticatiorManagerBuilder auth ) 方法,可以 自定义用户认证

  • 针对 自定义用户认证Spring Security 提供了 多种自定义认证方式,包括有:

    In-Memory Authentication ( 内存身份认证 )
    JDBCAuthentication( JDBC 身份认证 )

    LDAP Authentication( LDAP 身份认证 )
    AuthenticationProvider(身份认证提供商)
    UserDetailsService( 身份详情服务 )


    下面我们选取其中 3个比较有代表性方式讲解如何 实现自定义用户认证

4.1 “内存” 身份认证 ( In-Memory Authentication ) - ( 开发测试环境中使用,无法用于"实际生产环境" ) :

  • In-Memory Authentication ( 内存身份认证 ) 是 最简单身份认证方式 ( 用户凭证 :如 用户名密码角色存储在内存中,当用户尝试登录时,应用程序会在 内存查询和匹配是否有该用户 ) ,主要 用于Security安全认证体验测试
  • 自定义内存身份认证时,只需要在 重写configure ( AuthenticationManagerBuilder auth ) 方法中定义测试用户即可
  • 下面通过 Spring Boot 整合 Spring Security 实现 内存身份认证具体步骤如下
① 自定义 WebSecurityConfigurerAdapter 配置类 ( 创建一个配置类,该类继承 WebSecurityConfigurerAdapter类 )
  • 上面已经创建的项目基础上,创建 配置类 : SecurityConfig,该继承 WebSecurityConfigurerAdapter

    SecurityConfig.java

    import org.springframework.security.config.annotation.web.configuration.*;@EnableWebSecurity //开启MVC Security 安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter{ //关于Spring Boot 安全管理 (安全框架) 的 配置类}
    

    上述代码中自定义了一个 继承WebSecurityConfigurerAdapterSecurityConfig 配置类,用于进行 MVC Security 自定义配置,该类上方的 @EnableWebSecurity 注解是一个组合注解其效果等同于 :
    @Configuration@lmport@EnableGlobalAuthentication组合用法,关于这些注解介绍具体如下:

    注解描述
    @Configuration 注解标记该类SpringBoot 的 " 配置类"。
    @lmport 注解根据pom.xml导入Web模块Security模块 进行 自动化配置
    @EnableGlobalAuthentication 注解用于 开启自定义全局认证
② 使用 “内存” 进行 “身份认证”
  • 自定义SecurityConfig 类重写 configure( AuthenticationManagerBuilder auth )方法 , 并在该方法使用内存身份认证方式进行 自定义用户认证 :

    SecurityConfig.java

    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.configuration.*;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity //开启MVC Security 安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter{ //关于 "内存身份认证" 的 配置类/*** 重写configure()方法,并在该方法中使用"内存身份认证"的方式进行"自定义用户认证"*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 (创建一个"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();/*① auth.inMemoryAuthentication() : 将创建一组"硬编码"的 "用户名和密码",它们将用于身份验证 ,这种方式在"开发和测试环境"中非常有用,但在生产环境中可能不是最佳选择,因为用户名和密码直接存储在代码中。② 在Spring Security中配置了 "内存" 中的"用户身份验证" , 配置了两个用户 ,这两个用户可以直接用于身份验证,无需连接"数据库"或"其他外部数据源"。*/auth.inMemoryAuthentication()//设置一个"密码编辑器",用于在存储密码之前对其进行编码,这很重要,因为不能以明文形式存储密码。密码编码器可以将密码转换为一个不可逆的散列值.passwordEncoder(encoder)//为用户名"张三"创建一个用户,密码为"123456"(已编码),该用户具有"common角色".withUser("张三").password(encoder.encode("123456")).roles("common")//这是链式编程的一个常见模式,用于分隔不同的配置部分。.and()//为用户名"李四"创建一个用户,密码为"123456"(已编码),该用户具有"common角色".withUser("李四").password(encoder.encode("123456")).roles("vip");//配置了内存中的用户身份验证,设置了两个账户,两个用户可以直接用于身份验证,而无需连接数据库或其他外部数据源。}}
    

    上面的代码文件中,重写WebSecurityConfigurerAdapter 类configure ( AuthenticationManageBuilder auth ) 方法,并在该方法中使用内存身份认证的方式自定义了认证用户信息定义用户认证信息时,设置了两个用户,包括用户名密码角色
    ( 存储用户凭证 / 登录信息内存中,当用户进行登录时,将登录信息内存信息进行比对,进行“身份认证” )


    注意点 :

    上面的代码文件中进行的 自定义用户认证时,需要注意以下几个问题
    (1) 从 Spring Security 5开始自定义用户认证必须
    设置密码编码器用于保护密码
    ,否则控制台会出现 “llegalArgumentException: There is no PasswordEncoder mapped for the idn"nul"”异常错误

    (2) Spring Security 提供了多种密码编码器,包括 BcryptPasswordEncoderPbkdf2PasswordEncoderScryptPasswordEncoder等,密码设置不限于本例中的 BcryptPasswordEncoder 密码编码器。

    (3) 自定义用户认证时可以定义用户角色roles也可以定义用户权限 authorities。在进行赋值时,权限通常是在角色值基础上添加“ROLE”前级。例如,authorities("ROLE_common”)roles(“common”)等效的

    (4) 自定义用户认证时,可以为 某个用户 “一次指定多个” 角色权限,例如 roles ( “common”,“vip” ) 或者 authorities(“ROLE common”," ROLE_vip")

③ 效果测试
  • 此时重新启动项目启动成功后,可以看到==**控制台没有默认发现没有默认安全管理时随机生成 **的 密码==了,如下图所示 :

    在这里插入图片描述


    此时通过浏览器访问 http://localhost:8080/ 访问 项目首页效果如下图所示

    在这里插入图片描述

    从上图可以看出 执行 http://localhost:8080/ 访问首页同样自动跳转到了用户登录页面“http://localhost:8080/login”。
    如果输入的用户名或者密码错误,会出现相应错误提示效果如下图1所示。如果输入的用户名密码正确,那么会跳转进入网站首页效果如下图2所示

    在这里插入图片描述

    在这里插入图片描述

    此时点击电影名称同样可以查看电影详情,说明通过内存身份认证方式实现了自定义用户认证

    实际开发中用户都是在 页面注册登录 时进行认证管理的,而 非在程序内部 使用 内存管理的方式手动控制注册用户,所以上述使用内存身份认证的方式 无法用于实际生产,只可以作为初学者测试使用,或者可在测试开发环境使用。 )

4.2 “JDBC” 身份认证 ( JDBC Authentication ) - ( 将"用户登录信息" 与 “数据库信息” 进行 比对,判断是否是"合法用户" ) :

  • JDBC 身份认证 : JDBC Authentication , 是通过 JDBC 连接数据库已有用户身份进行认证,下面通过一个案例讲解 JDBC 身份认证
① 数据准备
  • JDBC 身份认证本质是使用 数据库中已有的用户信息在项目中实现用户认证服务,所以需要提前准备好相关数据。这里我们使用之前创建springbootdata数据库,在该数据库中创建3个表t_customert_authorityt_customer_authority,并预先插入几条测试数据。

    security.sql


    注意点 :

    使用 JDBC 身份认证方式创建用户/权限表以及初始化数据时,应特别注意以下几点 :

    (1) 创建用户表t_customer 时,用户名 username 必须唯一,因为 Security 在进行用户查询时是 先通过 username 定位是否存在唯一用户 的。

    (2) 创建用户表t_customer 时,必须额外定义一个 tinyint类型字段 (对应 boolean 类型属性,例如示例中的 valid ),用于 校验用户身份是否合法 (默认都是合法的)

    (3) 初始化用户表 t_customer 数据时,插入的用户密码 password 必须是对应 编码器编码后的密码,例如示例中的 密码:y2ay10y5ooQl8dir8jv0/gCa1Six.GpzAdIPf6pMgdminZ/3ijYzivCyPIfK 是使用 BcryptPasswordEncoder 密码加密后的形式 ( 对应的原始密码123456)。因此
    自定义配置类 中进行 用户密码查询 时,必须 使用与数据库密码统一密码编码器进行 编码

    (4)初始化权限表 t_authority 数据时,权限 authority必须 带有“ROLE ”前缀,而 默认用户角色值 则是 对应权限值去掉“ROLE”前缀

② 添加 “JDBC 连接数据库” 的 “依赖启动器”
  • pom.xml文件中,添加 Mysql数据库连接驱动的依赖JDBC连接依赖 :

    <!-- JDBC数据库连接启动器-->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency><!-- Mysql数据连接驱动-->
    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
    </dependency>
    
③ application.properties 中 进行 “数据库连接配置”
#Mysql数据库连接配置 (配置数据库信息)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=root
④ 使用 JDBC 进行 “身份认证”
  • 完成准备工作后,在 configure ( AuthenticationManagerBuilder auth )方法 中使用 JDBC 身份认证的方式进行自定义用户认证代码例子如下

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import javax.sql.DataSource;//使用 JDBC 进行身份认证
    @EnableWebSecurity //开启MVC Security 安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter{ //关于 "JDBC身份认证" 的 配置类@Autowiredprivate DataSource dataSource;/*** 重写configure()方法,并在该方法中使用"JDBC身份认证"的方式进行"自定义用户认证"*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 (创建一个"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();/**使用 JDBC 进行身份认证*/String userSQL ="select username,password,valid from t_customer " +"where username = ?";String authoritySQL="select c.username,a.authority from t_customer c,t_authority a,"+"t_customer_authority ca where ca.customer_id=c.id " +"and ca.authority_id=a.id and c.username =?";//使用 JDBC 进行身份认证auth.jdbcAuthentication()//设置密码编码器.passwordEncoder(encoder)// 设置数据源.dataSource(dataSource)//设置一个自定义的SQL查询,用于根据"用户名"查找"用户信息".usersByUsernameQuery(userSQL)//设置一个自定义的SQL查询,用于根据"用户名"查找"用户的权限信息".authoritiesByUsernameQuery(authoritySQL);}
    }
    

    上面的代码中,先使用 @Autwired 注解装配了 DataSource 数据源重写的方法 : configure( AuthenticationManagerBuilderaut ) 方法中使用 JDBC 身份认证的方式进行身份认证

    使用 JDBC 身份认证 时,首先需要对 密码 进行 编码设置 ( 必须与数据库中用户密码加密方式一样 ) , 然后需要加载 JDBC 进行认证连接数据源 DataSource ; 最后,执行SQL语句,实现 通过用户名 username 查询用户信息用户权限

⑤ 效果测试

和之前的访问url测试流程一样的

4.3 “UserDetailsService” 身份认证 :

  • 对于 用户流量较大项目来说,频繁地使用JDBC 进行 数据库查询认证不仅麻烦,而且 会降低网站响应速度对于一个 完善的项目来说,如果某些业务 已经 实现了用户信息查询服务 ,就 没必要使用 JDBC 进行身份认证 了。
  • "UserDetailsService" 身份认证 代码 案例如下
① 基础项目文件准备

创建项目 : ( 查询mysql数据库用的mybatis框架 )

在这里插入图片描述


项目结构

在这里插入图片描述


pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.myh</groupId><artifactId>chapter_21</artifactId><version>0.0.1-SNAPSHOT</version><name>chapter_21</name><description>chapter_21</description><properties><java.version>1.8</java.version></properties><dependencies><!--  该依赖中的version不能省略,因为其是阿里巴巴为了迎合Springboot而有,不是Springboot自己制作的  --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--       <dependency>-->
<!--          <groupId>org.thymeleaf.extras</groupId>-->
<!--          <artifactId>thymeleaf-extras-springsecurity6</artifactId>-->
<!--       </dependency>--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!--       <dependency>-->
<!--          <groupId>org.junit.jupiter</groupId>-->
<!--          <artifactId>junit-jupiter</artifactId>-->
<!--          <version>RELEASE</version>-->
<!--          <scope>test</scope>-->
<!--       </dependency>--></dependencies><!--    <build>-->
<!--       <plugins>-->
<!--          <plugin>-->
<!--             <groupId>org.springframework.boot</groupId>-->
<!--             <artifactId>spring-boot-maven-plugin</artifactId>-->
<!--          </plugin>-->
<!--       </plugins>-->
<!--    </build>--></project>

导入 Sql文件 ( 创建数据库表 ) :
security.sql


Customer.java

import java.io.Serializable;public class Customer implements Serializable { //数据库表对应的实体类private Integer id;//表示数据库表中“字段名”对应的属性 (如果“字段名”和“属性名”一致,则可不用配置该注解)private String username;private String password;private Integer valid;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Integer getValid() {return valid;}public void setValid(Integer valid) {this.valid = valid;}@Overridepublic String toString() {return "Customer{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", valid=" + valid +'}';}
}

Authority.java

import java.io.Serializable;public class Authority implements Serializable {private Integer id;private String authority;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getAuthority() {return authority;}public void setAuthority(String authority) {this.authority = authority;}@Overridepublic String toString() {return "Authority{" +"id=" + id +", authority='" + authority + '\'' +'}';}
}

CustomerMapper.java

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface CustomerMapper {/***  根据username查询信息*/@Select("select username,password,valid from t_customer where username =#{username}")Customer findByUsername(String username);
}

AuthorityMapper.java

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface AuthorityMapper {String authoritySQL = "select a.authority from t_customer c, t_authority a, t_customer_authority ca " +"where ca.customer_id = c.id " +"and ca.authority_id = a.id " +"and c.username = #{username}";/***  根据username查询"权限信息"*/@Select(authoritySQL)List<Authority> findAuthoritiesByUsername(String username);}

主程序启动类 :

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.myh.chapter_21.mapper") //将该包下的所有接口都加入到IOC容器中
public class Chapter21Application {public static void main(String[] args) {SpringApplication.run(Chapter21Application.class, args);}
}

application.properties

#allowPublicKeyRetrieval=true
#配置Mysql数据库信息
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root#添加并配置第三方数据源Druid(数据库连接池)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#初始化时创建的连接数。当应用程序启动时,连接池会立即创建这么多连接。
spring.datasource.initialSize=20
#连接池中最小的空闲连接数。连接池会维护至少这么多的空闲连接,当空闲连接数低于这个数值时,连接池会创建新的连接。
spring.datasource.minIdle=10
#连接池中最大的活动连接数。这表示在任何时候,连接池中的活动(即被使用的)连接数不会超过这个数值。如果所有连接都在使用中,并且达到这个上限,那么新的数据库连接请求将被阻塞或拒绝,直到有连接可用。
spring.datasource.maxActive=100#配置Redis数据库连接信息/连接参数
spring.redis.host=127.0.0.1
#连接服务端口
spring.redis.port=6379
#连接密码 (默认为空)
spring.redis.password=123456

创建 html资源文件 :
index.html页面是项目首页页面commonvip文件夹中分别对应普通用户VIP用户可访问的页面

index.html :

<!DOCTYPE html>
<!-- 配置开启thymeleaf模板引擎页面配置 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>影视直播厅</title>
</head>
<body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
<h1 align="center">欢迎进入电影网站首页</h1>
<hr>
<h3>普通电影</h3>
<ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
</ul>
<h3>VIP专享</h3>
<ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/1}">猩球崛起</a></li>
</ul>
</body>
</html>

1.html : ( 其他三个页面以此类推 )

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<!-- th:href="@{/}" : 返回项目首页-->
<a th:href="@{/}">返回</a>
<h1>飞驰人生</h1>
.....
</body>
</html>
② 定义查询用户及角色信息的服务接口
  • 定义查询用户及角色信息的服务接口,在项目中创建一个 CustomerService业务处理类,用来通过用户名查询

    户信息权限信息 :

    CustomerService.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Service;import java.util.List;// 对用户
    @Service //加入到IOC容器
    public class CustomerService {@Autowiredprivate CustomerMapper customerMapper;@Autowiredprivate AuthorityMapper authorityMapper;@Autowiredprivate RedisTemplate redisTemplate; //通过 Redis API 的方式来进行 "Redis缓存"/*** 业务控制 : 使用唯一用户名查询用户信息*/public Customer getCustomer(String username) {Customer customer = null;//从Redis数据库中获取"缓存数据"ValueOperations valueOperations = redisTemplate.opsForValue();Object obj = valueOperations.get("customer_" + username);//获得指定名称的"缓存数据"//判断是否有该缓存数据if (obj != null) {customer = (Customer) obj;} else { //不存在该缓存数据,则从数据库中查询缓存数据customer = customerMapper.findByUsername(username); //根据username来在数据库中查询数据if (customer != null) {redisTemplate.opsForValue().set("customer_"+username,customer);}}return customer;}/*** 业务控制 : 使用"唯一用户名"查询用户权限*/public List<Authority> getCustomerAuthority(String username) {List<Authority> authorities = null;//尝试从Redis数据库中获得缓存数据Object obj = redisTemplate.opsForValue().get("authorities_" + username);if (obj != null) {authorities = (List<Authority>) obj;} else {//没找到缓存数据则从Mysql数据库中查询数据authorities = authorityMapper.findAuthoritiesByUsername(username);//如果查询到了数据if (authorities.size() > 0) {redisTemplate.opsForValue().set("authorities"+username,authorities);}}return authorities;}}
    
③ 定义 “UserDetailsService” 用于封装认证用户信息 ( 创建类实现 “UserDetailsService接口” , 在该类中封装 “认证用户的信息” )
  • UserDetailsService接口Security 提供的用于 封装认证用户信息接口,该接口提供loadUserByUsername(Strings) 方法用于通过用户名加载用户信息。使用 UserDetailsService 进行 身份认证时,自定义一个UserDetailsService接口实现类,通过loadUserByUsername(String s)方法 封装用户详情信息返回 UserDetails 对象Security 认证使用

    UserDetailServiceImpl.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;//自定义一个UserDetailsService接口实现"用户认证信息"封装 : 在其中封装"用户认证信息"
    @Service
    public class UserDetailServiceImpl implements UserDetailsService { //实现 UserDetailsService接口@Autowiredprivate CustomerService customerService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {/**通过业务方法(业务层类)获取用户以及权限信息*///根据username获得Customer对象信息Customer customer = customerService.getCustomer(username);//根据username获得 "权限信息"List<Authority> authorities = customerService.getCustomerAuthority(username);/*** 对用户权限进行封装*/List<SimpleGrantedAuthority> list = authorities//"权限信息"集合 转换为一个流(Stream) , 以便进行后续的流式操作.stream()/*使用map操作 / map()函数 来转换流中的每个元素 (将流中的"每一个元素"转换为 "另一种形式")具体分析:authority -> 获得流中的每一个元素,将其转换为另一种形式authority.getAuthority() : 获得 authority 这个元素对象的"权限信息" ( 是一个"权限信息"的字符串 )new SimpleGrantedAuthority(authority.getAuthority())) : 使用这个 "权限信息字符串" 创建一个 SimpleGrantedAuthority对象,该对象 是 Spring Security框架中用于表示 "授权信息" 的类*/.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))/*以下是一个终端操作,它告诉流如何收集其元素以生成一个结果。具体分析:.collect(Collectors.toList()) : 收集转换后的 SimpleGrantedAuthority对象,并将其放入一个新的列表中*/.collect(Collectors.toList());/***  创建 UserDetails (用户详情) 对象,并将该对象进行返回*/if (customer != null) {//用 username 、password 、权限信息集合 作为参数创建 UserDetails对象 ( 用户详情对象 )UserDetails userDetails = new User(customer.getUsername(), customer.getPassword(), list);return userDetails;} else {//如果查询的用户不存在 (用户名不存在 ) , 必须抛出异常throw new UsernameNotFoundException("用户名不存在!");}}
    }
    

    在上面的文件代码中,重写UserDetailsService接口loadUserByUsername( )方法 ,用于借助 CustomerService 业务处理类获取 用户信息权限信息 ,并通过 UserDetails 进行认证信息封装

    需要注意的是
    CustomerService 业务处理类获取User 实体类时必须对当前用户进行 非空判断,这里使用 throw 进行异常处理,如果 查询的用户为空 ,throw会抛出 UsernameNotFoundException 的异常。如果没有 使用 throw 异常处理 ,Security 将无法识别导致程序整体报错

④ 使用 UserDetailsService 进行身份认证 ( 创建一个配置类,该类继承 WebSecurityConfigurerAdapter类 , 类中重写的方法中调用 userDetailsService( )实现 “UserDetailsService身份认证” )
  • SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity //开始MVC security身份认证
    public class SecurityConfig extends WebSecurityConfigurerAdapter { // 该类为 : 进行 "UserDetailsService"身份认证 的 "配置类"@Autowiredprivate UserDetailServiceImpl userDetailService; //该类为 配置好了"UserDetailsService"身份认证 信息的类,使用该类来进行 "UserDetailsService"身份认证@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailService)//设置"密码编辑器".passwordEncoder(encoder);}
    }
    
⑤ 效果测试

和之前的访问url测试流程一样的


至此 ,关于 Spring Boot 整合 Spring Security 中的 自定义用户认证 知识讲述完毕内存身份认证最为简单,主要用作测试新手体验; JDBC 身份认证UserDetailsService 身份认证实际开发中使用较多,而这两种认证方式的选择,主要根据实际开发中已有业务支持来确定

相关文章:

Spring Boot | Spring Security ( SpringBoot安全管理 )、Spring Security中 的 “自定义用户认证“

目录 : Spring Boot 安全管理 &#xff1a;一、Spring Security 介绍二、Spring Security 快速入门2.1 基础环境搭建 :① 创建Spring Boot 项目② 创建 html资源文件③ 编写Web控制层 2.2 开启安全管理效果测试 :④ 添加 spring-boot-starter-security 启动器⑤ 项目启动测试 三…...

力扣经典150题第五十五题:逆波兰表达式求值

目录 题目描述和要求示例解释解题思路算法实现复杂度分析测试和验证总结和拓展参考资料 题目描述和要求 给你一个字符串数组 tokens&#xff0c;表示一个根据逆波兰表示法表示的算术表达式。请你计算该表达式&#xff0c;并返回一个表示表达式值的整数。 注意&#xff1a; 有…...

C#队列(Queue)的基本使用

概述 在编程中&#xff0c;队列&#xff08;Queue&#xff09;是一种常见的数据结构&#xff0c;它遵循FIFO&#xff08;先进先出&#xff09;的原则。在C#中&#xff0c;.NET Framework提供了Queue<T>类&#xff0c;它位于System.Collections.Generic命名空间下&#x…...

预训练模型介绍

一、什么是GPT GPT 是由人工智能研究实验室 OpenAI 在2022年11月30日发布的全新聊天机器人模型, 一款人工智能技术驱动的自然语言处理工具 它能够通过学习和理解人类的语言来进行对话, 还能根据聊天的上下文进行互动,能完成撰写邮件、视频脚本、文案、翻译、代码等任务 二、 为…...

Pandas入门篇(三)-------数据可视化篇3(seaborn篇)(pandas完结撒花!!!)

目录 概述一、语法二、常用单变量绘图1. 直方图&#xff08;histplot&#xff09;2. 核密度预估图&#xff08;kdeplot&#xff09;3. 计数柱状图&#xff08;countplot&#xff09; 三、常用多变量绘图1.散点图(1) scatterplot(2)regplot 散点图拟合回归线(3)jointplot 散点图…...

SpringBoot中阿里OSS简单使用

官方文档:Java跨域设置实现跨域访问_对象存储(OSS)-阿里云帮助中心 1.pom中引入依赖 <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version> </dependency> 如…...

websocket简介

服务端推送消息给浏览器 WebSocket 教程 - 阮一峰的网络日志...

Linux的shell外壳

Shell外壳 在计算机领域&#xff0c;“shell”&#xff08;外壳&#xff09;是指一种用户界面&#xff0c;提供了访问操作系统服务的方式。Shell 是用户与操作系统之间的桥梁&#xff0c;它解释并执行用户输入的命令。 Shell 的主要功能包括&#xff1a; 命令解释&#xff1a…...

支付宝支付流程

第一步前端&#xff1a;点击去结算&#xff0c;前端将商品的信息传递给后端&#xff0c;后端返回一个商品的订单号给到前端&#xff0c;前端将商品的订单号进行存储。 对应的前端代码&#xff1a;然后再跳转到支付页面 // 第一步 点击去结算 然后生成一个订单号 // 将选中的商…...

打招呼得不到回复,跟头像还有关系?

现在很多人有个想法,那就是觉得某些公司是不是为了某些目的才往出发的招聘信息啊,其实他们并不需要招聘新员工。 目录 已读不回? 头像很重要 选择自己的精修照片 已读不回? 很有这种可能,但你细心观察会发现,的确有很多大公司,...

【网络原理】HTTPS 的工作过程

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 【网络…...

Python语言在地球科学中地理、气象、气候变化、水文、生态、传感器等数据可视化到常见数据分析方法的使用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;Python能够运行在Linux、Windows、Macintosh、AIX操作系统上及不同平台&#xff08;x86和arm&#xff09;&#xff0c;Python简洁的语法和对动态输入的支持&#xff0c;再加上解释性语言的本质&…...

【JVM】class文件格式,JVM加载class文件流程,JVM运行时内存区域,对象分配内存流程

这篇文章本来只是想讲一下class文件格式&#xff0c;讲着讲着越讲越多。JVM这一块吧&#xff0c;知识比较散比较多&#xff0c;如果深研究下去如死扣《深入理解Java虚拟机》&#xff0c;这本书很深很细&#xff0c;全记住是不可能的&#xff0c;其实也没必要。趁这个机会直接把…...

GPU系列(六)-NVIDIA GPU 驱动安装

1. 安装驱动 1.1 查看系统是否识别显卡 lspci | grep -i vga03:00.0 VGA compatible controller: NVIDIA Corporation GP102 [TITAN X] (rev a1) 0a:00.0 VGA compatible controller: Matrox Electronics Systems Ltd. G200eR2 (rev 01) 识别出显卡为 NVIDIA 的 TITAN X。 …...

第十五届蓝桥杯总结

因为本人不是计院的&#xff0c;以后可能也不会打算法类的竞赛了&#xff0c;故作此总结&#xff0c;纪念我四个月的算法学习经历&#xff0c;还算是对算法有了一定的基础&#xff0c;碰运气拿下了湖北b组省二&#xff0c;个人感觉比赛题目没有第十四届难&#xff0c;感觉就是纯…...

Linux驱动开发——(八)Linux异步通知

目录 一、异步通知简介 二、信号处理 2.1 驱动程序中的处理 2.1.1 fasync_struct结构体 2.1.2 fasync操作函数 2.1.3 kill_fasync函数 2.2 应用程序中的处理 三、驱动代码 一、异步通知简介 异步通知的核心就是信号。信号类似于硬件上使用的中断&#xff0c;只不过信号…...

Docker知识点汇总表格总结

Docker容器给我的一个很直观的感受就是将项目以及中间件安装变得比较简单直接&#xff0c;运行维护起来也更方便。之前做的一些微服务项目也是用docker来部署&#xff0c;现在很多开源的项目也流行使用docker来部署&#xff0c;简化了很多手动安装和配置的步骤&#xff0c;将项…...

Golang中实现调用Windows API向指定目标发送ARP请求

简介 Go库中很多实现的arp都是支持osx/linux/bsd之类的&#xff0c; 但几乎没有支持windows的&#xff0c; 也试了一些方式&#xff0c; 目前还是选用调用windows的API&#xff0c; 记录一下这一次windows的API的调用经验。 实现 代码 package main/* #cgo CFLAGS: -I. #cgo …...

这是一个简单的照明材料网站,后续还会更新

1、首页效果图 代码 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>爱德照明网站首页</title><style>/*外部样式*/charset "utf-8";*{margin: 0;padding: 0;box-sizing: border-box;}a{text-dec…...

【设计模式】之模板方法模式

系列文章目录 【设计模式】之策略模式 【设计模式】之责任链模式 文章目录 系列文章目录 前言 一、什么是模板方法模式 定义 角色 二、为什么要使用模板方法模式 优点 缺点 三、案例 普通案例 模拟Servlet过程案例 总结 前言 今天给大家介绍23种设计模式中的模板方法模式&a…...

【系统架构师】-选择题(十一)

1、紧耦合多机系统一般通过&#xff08;共享内存&#xff09;实现多机间的通信。对称多处理器结构&#xff08;SMP&#xff09;属于&#xff08; 紧耦合&#xff09;系统。 松耦合多机系统又称间接耦合系统,—般是通过通道或通信线路实现计算机间的互连。 2、采用微内核的OS结构…...

前端开发攻略---介绍HTML中的<dialog>标签,浏览器的原生弹框。

1、演示 2、介绍 <dialog> 标签用于定义对话框&#xff0c;即一个独立的窗口&#xff0c;通常用来显示对话框、提示框、确认框等弹出式内容。在对话框中&#xff0c;可以包含文本、表单元素、按钮等内容&#xff0c;用户可以和这些内容进行交互。 3、兼容性 4、示例代码 …...

让外贸业绩翻倍的销售话术分享

业绩翻三倍的话术&#xff0c;今后无论你遇到挑剔、犹豫、理智的顾客&#xff0c;都能轻松搞定。点赞存起来慢慢看&#xff0c;以免找不到。 与客户有效沟通技巧5的20句金句 业绩翻 3 倍&#xff0c;今后无论你遇到挑剔、犹豫、理智的顾客&#xff0c;都能轻松搞定。点赞存起来…...

观测与预测差值自动变化系统噪声Q的自适应UKF(AUKF_Q)MATLAB编写

简述 基于三维模型的UKF&#xff0c;设计一段时间的输入状态误差较大&#xff0c;此时通过对比预测的状态值与观测值的残差&#xff0c;在相应的情况下自适应扩大系统方差Q&#xff0c;构成自适应无迹卡尔曼滤波&#xff08;AUKF&#xff09;&#xff0c;与传统的UKF相比&…...

虚拟数据中心

创建数据中心和连接宿主机 DRS:收集群集内所有主机和虚拟机的资源使用情况信息&#xff0c;并根据特定的运行状况给出建议或迁移虚拟机HA:如果一台主机出现故障&#xff0c;则该主机上运行的所有虚拟机都将立即在同一群集的其他主机上重新启动EVC:增强型vMotionVirtual SAN:集中…...

解决Blender导出FBX文件到Unity坐标轴错误的问题

发现Blender的模型导入到Unity里面有问题,简单研究了下发现是坐标系不同,Unity使用的是左手坐标系,Blender使用的是右手坐标系 。 下面直接将如何解决 首先忽略Blender的右手坐标系以及Z轴朝上的事&#xff0c;依照unity坐标系情况修改模型物体的旋转&#xff0c;以Blender猴…...

基于微信小程序的校园二手闲置物品交易平台的设计与实现

基于微信小程序的校园二手闲置物品交易平台的设计与实现 “Design and Implementation of a Campus Second-Hand Marketplace Platform based on WeChat Mini Program” 完整下载链接:基于微信小程序的校园二手闲置物品交易平台的设计与实现 文章目录 基于微信小程序的校园二…...

java中多线程的3种实现方法

1.继承Thread类 优点&#xff1a;代码简单&#xff0c;可以直接使用Thread类里面的方法。 缺点&#xff1a;扩张性较差&#xff0c;应为在java中&#xff0c;一个类只能继承一个父类。 2.实现Runnable接口 3.实现Callable接口 2和3的优缺点是一样的 优点&#xff1a;扩展性强&…...

【Docker】docker compose服务编排

docker compose 简介 Dockerfile模板文件可以定义一个单独的应用容器&#xff0c;如果需要定义多个容器就需要服务编排。 docker swarm&#xff08;管理跨节点&#xff09; Dockerfile可以让用户管理一个单独的应用容器&#xff1b;而Compose则允许用户在一个模板&#xff08…...

elementui的el-select+el-tree+el-input实现可搜索的下拉树组件

部分实现代码如下 <template> <div><el-selectv-model"item.TableName"placeholder"请选择":disabled"!item.disabled"visible-change"handleVisible"ref"TableName"><el-input placeholder"请输…...

网站备案号怎么放/网站推广怎样做

目录 粒子群算法(1)----粒子群算法简介 粒子群算法(2)----标准的粒子群算法 粒子群算法(3)----标准的粒子群算法(局部版本) 粒子群算法(4)----粒子群算法分类 粒子群算法(5)-----标准粒子群算法的实现 粒子群算法(6)-----几个适应度评价函数 博文转载位置 粒子群算法(1)--…...

做网站白云/查询seo

https://vjudge.net/problem/POJ-1125 题意&#xff1a; 题意不是很好理解&#xff0c;首先输入一个n&#xff0c;表示有n个股票经纪人&#xff0c;接下来输入n行&#xff0c;每行第一个数m为该股票经纪人认识的经纪人数&#xff0c;然后输入m对数&#xff08;a&#xff0c;t&a…...

固镇网站建设哪家好/如何写推广软文

直接点击跳转到系统设置界面, 设置通知定位等等 在操作之前, 你需要info中设置 URLTypes, 如下图所示 直接通过 root *什么来配置NSArray *dataArray [ {"系统设置":"prefs:rootINTERNET_TETHERING"}, {"WIFI设置":"prefs:rootWIFI&quo…...

进一步加强 政府门户网站建设管理工作的通知/关键词排名提升工具

为什么80%的码农都做不了架构师&#xff1f;>>> 在 centos 7.2 系统内&#xff0c; 网卡的配置文件在&#xff1a; /etc/sysconfig/network-scripts/ 下。 命名规则&#xff1a; ifcfg-xxxx。 xxx为设备名称。 通过分析 network 服务的执行脚本&#xff1a; /etc…...

网站建设集团/网络营销的概述

系统上rpm命令管理程序包&#xff1a; 安装、卸载、升级、查询、校验、数据库维护 安装&#xff1a; rpm {-i|--install} [install-options] PACKAGE_FILE ... -v: verbose -vv: -h: 以#显示程序包管理执行进度&#xff1b;每个#表示2%的进度 rpm -ivh PACKAGE_FILE ... [inst…...

专门做海报的网站/近三年成功的营销案例

在本地测试&#xff0c;使用docker部署不用在意环境 java测试项目&#xff1a; web框架&#xff1a;spring boot 框架项目管理&#xff1a;maven数据库&#xff1a;redis postgres mongo部署相关&#xff1a;nginx docker-compose项目目录&#xff1a; 项目结构如图&#xf…...