【第3期】Springboot集成SpringSecurity+RSA+ECS免密登录

本期简介

RSA是非常安全的非对称加解密算法,单纯的RSA的原理和使用网络资料较多,本期不细讲RSA的原理,主要讲解实战,如何与Springboot+SpringSecurity集成起来,做到在安全框架基础上,对用户的密码进行加密存储,解密认证。同时,平时我们登录ECS服务器大多数情况都是账号密码登录形式,麻烦且容易忘记密码,本期最后会讲如何通过RSA的密钥来实现ECS免密自动登录。

  • 本期要点:
  1. 如何生成RSA公钥文件和私钥文件
  2. 获取公钥文件和私钥文件转换成JDK的密钥文件对象
  3. 简单的验证加密和解密
  4. 与Springboot、SpringSecurity如何集成
  5. 集成后验证用户注册的加密存储
  6. 集成后验证用户登录的解密验证
  7. 实现ECS的免密登录

一、如何生成RSA公钥文件和私钥文件

方式一:通过命令行生成

  • 命令:ssh-keygen -t -rsa -C xxxxx@mail.com
  • 说明:-t rsa表示生成的密钥使用的算法为RSA,-C表示用户邮箱号,会追加在公钥文件文本后
    在这里插入图片描述
    查看生成结果,可以看到生成了new_id_rsa私钥文件和new_id_rsa.pub公钥文件
    如果前面一步执行命令一路回车生成的默认密钥路径分别是

    /Users/本机用户名/.ssh/id_rsa
    /Users/本机用户名/.ssh/id_rsa.pub
    在这里插入图片描述

方式二:通过代码自动生成

  • 首先创建RsaUtils工具类方法
    /*** 根据密文,生存RSA公钥和私钥,并写入指定文件** @param publicKeyFilename  公钥文件路径* @param privateKeyFilename 私钥文件路径* @param password           生成密钥的密码*/public static void generateKey(String publicKeyFilename, String privateKeyFilename, String password, int keySize) throws Exception {// 创建KeyPairGenerator对象,指定算法为RSAKeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);if (password == null) {// 初始化KeyPairGenerator对象,设置密钥长度为2048位keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), JCAUtil.getSecureRandom());} else {SecureRandom secureRandom = new SecureRandom(password.getBytes(StandardCharsets.UTF_8));// 初始化KeyPairGenerator对象,设置密钥长度为2048位keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);}// 生成KeyPair对象,即公钥和私钥KeyPair keyPair = keyPairGenerator.genKeyPair();// 获取公钥并写到文件byte[] publicKeyBytes = keyPair.getPublic().getEncoded();publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);writeFile(publicKeyFilename, publicKeyBytes);// 获取私钥并写到文件byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);writeFile(privateKeyFilename, privateKeyBytes);}public static void generateKey(String pubKeyFileName, String priKeyFileName) throws Exception {generateKey(pubKeyFileName, priKeyFileName, null, DEFAULT_KEY_SIZE);}private static byte[] readBytesFromFile(String fileName) throws Exception {return Files.readAllBytes(new File(fileName).toPath());}private static void writeFile(String destPath, byte[] bytes) throws IOException {File dest = new File(destPath);if (dest.exists()) {Files.write(dest.toPath(), bytes);return;}boolean created = dest.createNewFile();if (!created) {log.warn("写入密钥内容到文件{}失败,请检查!", destPath);}Files.write(dest.toPath(), bytes);}public static void main(String[] args) throws Exception {RsaUtils.generateKey("/Users/本机用户名/.ssh/id_rsa.pub", "/Users/本机用户名/.ssh/id_rsa");}

从上方代码实现的功能就是ssh-keygen -t rsa功能,执行该main方法:
从输出日志看,成功生成了公钥和私钥文件
在这里插入图片描述
从本机对应目录检查下生成结果:
在这里插入图片描述

二、获取公钥文件和私钥文件转换成JDK的密钥文件对象

到目前为止已经生成了公钥文件和私钥文件,要实现加解密,还需要将公钥文件和私钥文件读取到jvm内存并转换成对应的公钥文件对象和私钥文件对象

获取公钥文件

公钥文件类java.security.PublicKey是属于jdk里面的类,把公钥文件转换成该对象实例

    /*** 从文件中读取公钥为PublicKey对象** @param filename 公钥保存路径,相对于classpath* @return 公钥对象* @throws Exception 读取公钥抛出的异常类型*/public static PublicKey getPublicKey(String filename) throws Exception {byte[] bytes = readBytesFromFile(filename);byte[] decodeBytes = Base64.getDecoder().decode(bytes);X509EncodedKeySpec spec = new X509EncodedKeySpec(decodeBytes);KeyFactory factory = KeyFactory.getInstance(ALGORITHM);return factory.generatePublic(spec);}

获取私钥文件

私钥文件类java.security.PrivateKey是属于jdk里面的类,把私钥文件转换成该对象实例

    /*** 从文件中读取私钥为PrivateKey对象** @param filename 私钥保存路径,相对于classpath* @return 私钥对象* @throws Exception 读取私钥抛出的异常类型*/public static PrivateKey getPrivateKey(String filename) throws Exception {byte[] bytes = readBytesFromFile(filename);byte[] decodeBytes = Base64.getDecoder().decode(bytes);PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodeBytes);KeyFactory factory = KeyFactory.getInstance(ALGORITHM);return factory.generatePrivate(spec);}

完成这两个方法后便具备了通过代码进行加密和解密的功能,下面进行加解密的验证。

三、简单的验证加密和解密

  • RSA算法是公钥加密、私钥解密,所以公钥可以分发,私钥不能分发,一旦私钥泄漏,将是一场灾难

加密

加密方法,指定加密明文和公钥文件的路径

    /*** RSA公钥加密** @param plainText     明文* @param publicKeyPath 公钥文件路径* @return 密文* @throws Exception 加密过程中的异常信息*/public static String encrypt(String plainText, String publicKeyPath) throws Exception {// base64编码的公钥PublicKey publicKey = getPublicKey(publicKeyPath);// RSA加密Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] cipherBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(cipherBytes);}

解密

解密方法,指定解密密文和私钥文件的路径

    /*** RSA私钥解密** @param cipherText     密文* @param privateKeyPath 私钥文件路径* @return 明文*/public static String decrypt(String cipherText, String privateKeyPath) throws Exception {// 64位解码加密后的字符串byte[] inputBytes;inputBytes = Base64.getDecoder().decode(cipherText.getBytes(StandardCharsets.UTF_8));PrivateKey privateKey = getPrivateKey(privateKeyPath);// RSA解密Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, privateKey);return new String(cipher.doFinal(inputBytes));}

验证加解密

编写main方法,对明文先加密,再从密文解密,对比加解密前后的一致性

    public static void main(String[] args) throws Exception {String publicKeyPath = "/Users/本机用户名/.ssh/id_rsa.pub";String privateKeyPath = "/Users/本机用户名/.ssh/id_rsa";System.out.printf("公钥文件路径:%s\n", publicKeyPath);System.out.printf("私钥文件路径:%s\n\n", privateKeyPath);String plainText = "pass123456@2023!";String cipherText = encrypt(plainText, publicKeyPath);System.out.printf("加密前明文是%s\n", plainText);System.out.printf("加密后密文是%s\n\n", cipherText);String plainTextRecovery = decrypt(cipherText, privateKeyPath);System.out.printf("解密前密文是%s\n", cipherText);System.out.printf("解密后明文是%s\n", plainTextRecovery);System.out.printf("密码前后一致性:%s\n\n", plainText.equals(plainTextRecovery));}

验证结果:
从结果来看,是符合预期的,接下来将RSA集成到Springboot和SpringSecurity实现加解密注册登录验证
在这里插入图片描述

四、与Springboot、SpringSecurity如何集成

编写配置类

用于系统启动自动读取公私钥文件的路径

@Data
@Configuration
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {private String pubKeyFile;private String priKeyFile;
}

对应application-env.yml的配置:
在这里插入图片描述

启动自动加载公私钥

我们需要在系统启动的时候自动加载公私钥路径,并且将公私钥文件转换为PublicKey和privateKey的实例

@Data
@Configuration
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {private String pubKeyFile;private String priKeyFile;private PublicKey publicKey;private PrivateKey privateKey;/*** 系统启动的时候触发,将公钥文件从本机文件加载为公私钥对象* @throws Exception 公私钥加载异常*/@PostConstructpublic void createRsaKey() throws Exception {publicKey = RsaUtils.getPublicKey(pubKeyFile);privateKey = RsaUtils.getPrivateKey(priKeyFile);}
}

SpringSecurity集成RSA

SpringSecurity提供了一个PasswordEncoder接口,我们通过实现这个接口来创建自定义的 RSA加解密:


import com.snycedu.platform.auth.config.RsaKeyProperties;
import com.snycedu.platform.auth.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;@Slf4j
public class RSACryptPasswordEncoder implements PasswordEncoder {private RsaKeyProperties prop;public RSACryptPasswordEncoder(RsaKeyProperties prop) {this.prop = prop;}@Overridepublic String encode(CharSequence plainText) {try {return RsaUtils.encrypt(plainText.toString(), prop.getPublicKey());} catch (Exception exception) {log.error("加密异常:{}", exception.getMessage(), exception);}return "";}public String decode(CharSequence cipherText) {try {return RsaUtils.decrypt(cipherText.toString(), prop.getPrivateKey());} catch (Exception exception) {log.error("解密异常:{}", exception.getMessage(), exception);}return "";}@Overridepublic boolean matches(CharSequence plainText, String cipherText) {String decryptedPlainText = decode(cipherText);return StringUtils.equals(plainText, decryptedPlainText);}
}

在前面几期提到要创建SpringSecurity的安全配置类,现在在WebSecurityConfig中注册一个beanpasswordEncoder,其为RSACryptPasswordEncoder的对象实例

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic RSACryptPasswordEncoder passwordEncoder() {return new RSACryptPasswordEncoder(prop);}
}

在WebSecurityConfig重写configure(AuthenticationManagerBuilder auth) 方法,指定密码认证管理器为我们注册的自定义RSA加解密的bean,这样SpringSecurity就和RSA集成起来了。

    // 指定认证对象的来源public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());}

SpringSecurity如何进行账号密码认证:
通过用户的账号和密码创建了UsernamePasswordAuthenticationToken认证实例,authenticationManager.authenticate(authRequest)进行账号密码认证,这一步就需要用到前面的加解密的bean

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {UserPo sysUser = null;try {sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPo.class);UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());return authenticationManager.authenticate(authRequest);} catch (Exception exception) {// 这里省略了登录认证失败的逻辑,前几期有提到,可以翻看下}return null;}

五、集成后验证用户注册的加密存储

注册接口

注意:注册登录接口无需进行权限相关的验证,需从SpringSecurity中配置为白名单,前期有讲

    @Operation(tags = "用户注册")@PostMapping("/api/v1/register")public Response<LoginUser> register(@RequestBody @Validated RegisterRequest user) {LoginUser loginUser = registerService.register(user);return ResponseResult.success(loginUser);}

注册业务层的实现

注入BeanpasswordEncoder,用于注册时对用户的明文密码进行加密存储

    @AutowiredRSACryptPasswordEncoder passwordEncoder;@AutowiredUserService userService;@Override@Transactional(rollbackFor = Exception.class)public LoginUser register(RegisterRequest user) {// 先判定账号是否被占用int exits = userService.existsTheLoginName(user.getLoginName());if (exits > 0) {throw new BusinessException("此账号已被占用,请更换");}UserPo userPo = JsonUtils.copy(user, UserPo.class);userPo.setId(SnowflakeIdWorker.nextId());userPo.setPassword(passwordEncoder.encode(user.getPassword()));userPo.setCreator(user.getLoginName());userPo.setCreateTime(LocalDateTime.now());userPo.setStatus(LoginStatusEnum.INIT);UserPo registerUser = userService.add(userPo);return JsonUtils.copy(registerUser, LoginUser.class);}

调用注册接口注册并验证数据存储结果

  • 通过postman调用注册接口
    在这里插入图片描述
  • 断点调试加密前获取到的注册信息,可以看到是明文的密码
    在这里插入图片描述
  • 断点调试加密后的用户密码,可以看到已经是加密的密文了
    在这里插入图片描述

从插入sql的日志查看写入情况

2023-12-16 11:26:06.737 [http-nio-8080-exec-9] [INFO] [com.snycedu.platform.common.interceptor.MybatisSqlInterceptor:75] - 
========================user:user   pwd:123456  db:jdbc:mysql://127.0.0.1:33061/snycedu?serverTimezone=CTT&useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
[    SQL   ID]:com.snycedu.platform.auth.mapper.UserMapper.insert                                                       
[         SQL]:insert into common_user ( id, login_name, password, status, phone, born, fail_count, creator, create_time, modifier, modify_time ) values (16137259137024000, 'zhangsan', 'cO6gJvbweUnq5Tk3nLKTCdcnCw2ifXUpqlD0MMWWZzdNwC4RK+Rw6r/0xq73QgoJhxdVbxtk1VTNPbGrMj1oI8+Fx0I7Ir4K4XxcFB1haEYwHHvgLE0nicNYg9AFEygiZwDwW+O3da9kRtLX8445XqHrcIo51zPgWCWadEIfmfWLTzxgky5Omzhb5/rFpBtqVc3944MdOV8Si0JH1laOOo8GmICdxg2SHXvYWtcTWMhWhNu3eKydyLna+m31vv6FLhUUvUt1LLEgk7Sgthe2OyV2aKyG2qQudR5IU+v1y5EjcgLQCXuqjUhVWFk7Jaw3uzk1W9a243ag0LNtFhDY9Q==', INIT,,,, 'zhangsan', 2023-12-16T12:45:12.435, , 2023-12-16T12:45:12.439 )
[RETURN TOTAL]:                                                                                                         
[RETURN  DATA]:1                                                                                                        
[WASTE   TIME]:31(ms)                                                                                                   
========================================================================================================================2023-12-16 11:26:06.739 [http-nio-8080-exec-9] [INFO] [com.snycedu.platform.component.mybatis.base.service.impl.BaseServiceImpl:43] - 成功插入1条数据 

从数据库看,也是成功插入注册的信息
在这里插入图片描述

六、集成后验证用户登录的解密验证

登录接口

前期提到,SpringSecurity默认实现了登录接口,我们只需要把登录验证逻辑写在Filter中即可

  • 调用登录接口
    在这里插入图片描述

  • 登录UserLoginFilter断点查看账号密码如何认证的
    从下面断点可以看到,认证前登录传入的还是明文密码,数据库的密码是密文,如何进行密码匹配的:前面提到通过用户名和密码构造了UsernamePasswordAuthenticationToken的是实例,然后通过认证管理器beanauthenticationManager进行认证
    在这里插入图片描述

  • 认证管理器和密码解密器
    从下图可以看出,账号密码认证管理器中的加解密器就是我们自定义的RSACryptPasswordEncoder,继续断点,查看明文密码和密文是如何进行验证的,细心的读者其实已经发现了
    在这里插入图片描述

  • 明文密码与密文密码匹配机制
    从下图很明显可以看出,密码的验证就是在我们的RSACryptPasswordEncoder类中的matches方法,方法的参数1就是登录传入的明文密码,参数2就是数据库读取到的加密密文,将密文进行解密与明文密码进行匹配即可验证密码
    在这里插入图片描述

  • 登录验证
    不出意外,登录就可以成功了
    在这里插入图片描述

七、实现ECS的免密登录

既然主要围绕RSA讲,我们平时登录ECS大多是通过账号密码进行登录,每次都要输入密码,现在用RSA的公私钥进行自动验证登录

  • 正常密码登录

在这里插入图片描述

  • ECS开启公钥免密登录

    sudo vim /etc/ssh/sshd_config

修改下面2个配置远程ECS开启公钥免密登录及公钥文件位置
在这里插入图片描述

  • 本地生成免密公钥authorized_keys文件

cp id_rsa.pub authorized_keys

  • 免密登录
    在这里插入图片描述
    可以看到,直接ssh root@xx.xxx.xx.xx 即可免密登录到远程ECS

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

maven+spock

pom配置 话说JunitMockito的组合用起来是真难用&#xff0c;还是Spock的简单&#xff0c;尤其是参数化的测试。junit的Parameter是鸡肋&#xff0c;杂恶心&#xff1b;Theories用来也不爽。 <?xml version"1.0" encoding"UTF-8"?><project xm…

html的学习笔记

开发工具&#xff1a;vscode 文字标签 h1:一级标题&#xff0c;h2&#xff1a;二级标题h6 p&#xff1a;段落标签 hr&#xff1a;分隔线 br&#xff1a;换行 strong/b&#xff1a;文字加粗 ins/u:下划线 em/i&#xff1a;倾斜 del/s&#xff1a;删除线 媒体标签 图片…

Mybatis-plus介绍与入门

前言 MyBatis-Plus是在MyBatis基础上的一个增强工具库&#xff0c;旨在简化开发者的工作&#xff0c;提高开发效率&#xff0c;同时保留MyBatis的灵活性。使用 MyBatis-Plus 可以减少重复性的代码&#xff0c;简化常见的数据库操作 官方学习文档&#xff1a;MyBatis-Plus (bao…

死锁 + 条件变量 + 生产消费者模型

文章目录 死锁如何解决死锁问题呢&#xff1f;避免死锁 同步条件变量生产消费者模型 死锁 现象 &#xff1a; 代码不会继续往后推进了 问题 一把锁有没有可能产生死锁呢&#xff1f; 有可能 线程第一次申请锁成功&#xff0c;继续再次申请&#xff0c;第二次申请就失败了&am…

从纸笔到屏幕:我的CS笔记记录体验分享

前言 三年大学生活里&#xff0c;我花了很多时间在记录笔记上&#xff0c;也因为现有种类繁多的各种学习方式&#xff0c;和朋友一起走了很多弯路。纸笔&#xff0c;OneNote&#xff0c;Typora…… 想总结分享一下自己大学期间的学习笔记记录方式&#xff08;主要针对计算机学…

不同的葡萄品种的葡萄酒有什么共同特质?

在某种程度上几乎所有的葡萄酒都是混合的&#xff0c;在大多数葡萄酒产地&#xff0c;法律允许在单一品种葡萄酒中混入高达15%的另一种葡萄酒&#xff0c;且还能被称为由主要葡萄酿造的单一品种葡萄酒酒。这些单一品种葡萄酒混合了少量其他葡萄酒&#xff0c;是为了创造一个特质…

centos离线安装mosquitto

1.x86_64架构centos7操作系统mosquitto包 本次真正要安装的机器是x86_64架构的AMD Ryzen 3 &#xff0c;操作系统是centos7 先找一台能联网的centos7机器 添加 EPEL 软件库 yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm下载rpm包 …

【DataSophon】大数据管理平台DataSophon-1.2.1基本使用

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

通过WinCC基本功能实现批次查询及批次报表

谈到WinCC中的批次数据处理和批次报表&#xff0c;也许有人会想到PM-Quality这款专业的批次报表软件。但如果你的银子有限&#xff0c;批次报表要求又比较简单&#xff0c;不妨看看此文。 —《通过 WinCC 基本功能实现批次数据过滤查询以及打印批次数据报表》 实现的功能描述 …

中通单号查询,中通快递物流查询,对需要的单号进行备注

批量查询中通快递单号的物流信息&#xff0c;对需要的单号进行备注。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主界面左上角的“…

华为OD机试真题-查找一个有向网络的头节点和尾节点-2023年OD统一考试(C卷)

题目描述&#xff1a; 给定一个有向图&#xff0c;图中可能包含有环&#xff0c;图使用二维矩阵表示&#xff0c;每一行的第一列表示起始节点&#xff0c;第二列表示终止节点&#xff0c;如[0, 1]表示从0到1的路径。每个节点用正整数表示。求这个数据的首节点与尾节点&#xf…

linux(centos7)离线安装mysql-5.7.35-1.el7.x86_64.rpm-bundle.tar

1. 卸载mariadb相关rpm # 查找 rpm -qa|grep mariadb rpm -qa|grep mysql# 卸载 rpm -e --nodeps mariadb... rpm -e --nodeps mysql...2. 删除mysql相关文件 # 查找 find / -name mysql# 删除 rm -rf /var/lib/mysql...3. 查看是否有相关依赖&#xff0c;没有需安装 rpm -q…