背景

登录认证几乎是所有互联网应用的必备功能,传统的用户名-密码认证方式依然流行,如何避免用户名、密码这类敏感信息在认证过程中被嗅探、破解?

这里将传统的用户名、密码明文传输方式改为采用 RSA 的非对称加密算法密文传输,即使认证请求被网络抓包,只要私钥安全,则认证流程中的用户信息相对安全;

  1. 一般是生成RSA的密钥对之后,公钥存储在前端或后端(登录时每次请求后端返回公钥)进行加密,私钥存储在后端用于解密;
  2. 曾在实际的应用中看到过动态生成密钥对的做法,即公钥-私钥都是动态生成,每次请求都不一样,这与固定公钥-私钥的做法相比,性能上损耗较大,而在安全性上的收益并没有增加多少;因此这里采用固定密钥对的方式进行演示。

生成RSA密钥对

主要涉及三条命令:

# 生成RSA私钥
genrsa -out rsa_private_key.pem 1024# 把RSA私钥转换成PKCS8格式
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt# 生成RSA公钥
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
  • Windows操作系统: Win10

下载安装 OpenSSL :https://slproweb.com/products/Win32OpenSSL.html

打开 openssl.exe 所在目录,我这里是: D:\Program Files\OpenSSL-Win64\bin ,运行exe,执行上述三行命令实现 RSA 密钥对生成:

OpenSSL> genrsa -out rsa_private_key.pem 1024
Generating RSA private key, 1024 bit long modulus (2 primes)
.....................................+++++
................+++++
e is 65537 (0x010001)OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL/KFpxZ2ZJq4/f8
1oM2LX/aX1llPL6SlFbk5pBw1ESuQDVrcA8T4grdrFoEY6T2mNQAMiuzRKfYkS1l
Qx1C+L0HruqOPhFwDL7rxrDQU+8g/trCv+DQoMAbIcteqgxLQrvMZs1OuJrK0XpG
p4Ca7Wxfuk8HUynjQ9fhXIjWzWTjAgMBAAECgYBMUAARNFszPF77RNqiGQOftOdt
ra+u8KofrTLk1FBSB7e6ycYr6bBuvGeg5dA0Sn7jFDTiWJF/69dQZdN/qC9Kb0OV
jRtXDCSMHe1oRlvDr8tZKn9h9UljJHXrIapXJi5Z1eNQ3DW8ltgJbx/DpQrsSTYJ
JiWWpwfb6e+ub09JEQJBAOt+DAxec2h1Gq43Fc/fJ6hUmVl0VI0d5WkeVHezhutE
gYj29gkHkQin5VIMbXtutB/083vUm+Fxqc5EXdxzYIsCQQDQfb+gNZgBzeNhF/j5
IdqW68PpSOmWj2z9sVvAktSS9VzTt46haBvnjzIbES+uzJXoW0LI0H1zDlbvbtRV
HQAJAkEAz+kQMBdvowjIzok5y7ZEqBxQ66aGQ7TiZ2Vsw+YPt0VbbBZF8IDqro61
KzRnsLNzekdkdK6oFWmptr+rcse2swJARN10QSfSqK3n7/cqHqgm+nivgku6FCgV
uQovI0Gcg1oWKjxUGU45AVhUFYqstFERJumV+pybAzj2UCnMarykeQJAAkXb5Z7A
sb7wmLCDMoyfzJCn54k1VDEvGVcrn4SiME53wEyGnrYkyg8R84hO7rHLOnwz0PtZ
iLWuHpqd2OovmA==
-----END PRIVATE KEY-----OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
writing RSA key
  • Linux操作系统:CentOS7

同样,执行上述三行命令实现 RSA 密钥对生成:
Note:

  1. 后续编码实现时,使用Windows上生成的秘钥进行演示;
  2. 公钥、私钥用的是下图中红色椭圆标注出来的内容。

公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/yhacWdmSauP3/NaDNi1/2l9ZZTy+kpRW5OaQcNRErkA1a3APE+IK3axaBGOk9pjUADIrs0Sn2JEtZUMdQvi9B67qjj4RcAy+68aw0FPvIP7awr/g0KDAGyHLXqoMS0K7zGbNTriaytF6RqeAmu1sX7pPB1Mp40PX4VyI1s1k4wIDAQAB私钥:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL/KFpxZ2ZJq4/f81oM2LX/aX1llPL6SlFbk5pBw1ESuQDVrcA8T4grdrFoEY6T2mNQAMiuzRKfYkS1lQx1C+L0HruqOPhFwDL7rxrDQU+8g/trCv+DQoMAbIcteqgxLQrvMZs1OuJrK0XpGp4Ca7Wxfuk8HUynjQ9fhXIjWzWTjAgMBAAECgYBMUAARNFszPF77RNqiGQOftOdtra+u8KofrTLk1FBSB7e6ycYr6bBuvGeg5dA0Sn7jFDTiWJF/69dQZdN/qC9Kb0OVjRtXDCSMHe1oRlvDr8tZKn9h9UljJHXrIapXJi5Z1eNQ3DW8ltgJbx/DpQrsSTYJJiWWpwfb6e+ub09JEQJBAOt+DAxec2h1Gq43Fc/fJ6hUmVl0VI0d5WkeVHezhutEgYj29gkHkQin5VIMbXtutB/083vUm+Fxqc5EXdxzYIsCQQDQfb+gNZgBzeNhF/j5IdqW68PpSOmWj2z9sVvAktSS9VzTt46haBvnjzIbES+uzJXoW0LI0H1zDlbvbtRVHQAJAkEAz+kQMBdvowjIzok5y7ZEqBxQ66aGQ7TiZ2Vsw+YPt0VbbBZF8IDqro61KzRnsLNzekdkdK6oFWmptr+rcse2swJARN10QSfSqK3n7/cqHqgm+nivgku6FCgVuQovI0Gcg1oWKjxUGU45AVhUFYqstFERJumV+pybAzj2UCnMarykeQJAAkXb5Z7Asb7wmLCDMoyfzJCn54k1VDEvGVcrn4SiME53wEyGnrYkyg8R84hO7rHLOnwz0PtZiLWuHpqd2OovmA==

后端服务

基于 SpringBoot , SpringSecurity 实现用户认证功能。

项目依赖

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

SpringSecurity配置

注意放行认证接口,否则报错:403。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/auth/login").permitAll().anyRequest().authenticated().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// turn off csrf, or will be 403 forbidden.csrf().disable();}
}

用户信息配置

为了集中焦点在本篇的用户名-密码加密传输上,避免引入其他复杂性,这里采用内存型用户信息来演示,关于从数据库中获取用户信息,可参考6-SpringSecurity:数据库存储用户信息。

@Component
public class CustomUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return User.withUsername("dev").password(new BCryptPasswordEncoder().encode("123")).authorities("p1", "p2").build();}
}

认证接口

这里将私钥配置在 applicaiton.yml 中。

@RestController
@RequestMapping("auth")
@Slf4j
public class LoginController {@Value("${rsa.private_key}")private String privateKey;private final AuthenticationManagerBuilder authenticationManagerBuilder;public LoginController(AuthenticationManagerBuilder authenticationManagerBuilder) {this.authenticationManagerBuilder = authenticationManagerBuilder;}@PostMapping("/login")public String login(@RequestBody FormUser formUser, HttpServletRequest request) {log.info("formUser encrypted: {}", formUser);// 用户信息RSA私钥解密,方法一:自定义工具类:RSAEncrypt
//        String username = RSAEncrypt.decrypt(formUser.getUsername(), privateKey);
//        String password = RSAEncrypt.decrypt(formUser.getPassword(), privateKey);
//        log.info("Userinfo decrypted: {}, {}", username, password);// 用户信息RSA私钥解密,方法二:使用hutool中的工具类进行解密RSA rsa = new RSA(privateKey, null);String username = new String(rsa.decrypt(formUser.getUsername(), KeyType.PrivateKey));String password = new String(rsa.decrypt(formUser.getPassword(), KeyType.PrivateKey));log.info("Userinfo decrypted: {}, {}", username, password);// 核验用户名密码UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authentication);log.info("authentication: {}", authentication);return SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();}
}

自定义工具类进行解密

<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.12</version>
</dependency>
public class RSAEncrypt {/*** RSA公钥加密* @param str       待加密字符串* @param publicKey 公钥* @return 密文*/public static String encrypt(String str, String publicKey) {try {//base64编码的公钥byte[] decoded = Base64.decodeBase64(publicKey);RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, pubKey);return Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));} catch (Exception e) {throw new RuntimeException(e);}}/*** RSA私钥解密* @param str        已加密字符串* @param privateKey 私钥* @return 明文*/public static String decrypt(String str, String privateKey) {try {//64位解码加密后的字符串byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));//base64编码的私钥byte[] decoded = Base64.decodeBase64(privateKey);RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, priKey);return new String(cipher.doFinal(inputByte));} catch (Exception e) {throw new RuntimeException(e);}}
}

使用hutool中的工具类进行解密

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.0.6</version>
</dependency>

前端工程

基于 Vue3.0 , axios 实现极简登录页面。

Note:

  1. 前提需要有 Node.js 环境,可使用 nvm 进行 Node.js 的多版本管理;可参考https://heartsuit.blog.csdn.net/article/details/116665356
  2. npm install <package>默认会在依赖安装完成后将其写入package.json,因此安装依赖的命令都未附加save参数。
$ node -v
v12.16.1

安装vue-cli并创建项目

npm install -g @vue/cli
vue --version
vue create hello-world

刚开始的 package.json 依赖是这样:

  "dependencies": {"core-js": "^3.6.5","vue": "^3.0.0"},

集成Axios

  • 安装依赖
npm install axios

此时, package.json 的依赖变为:

  "dependencies": {"axios": "^0.21.1","core-js": "^3.6.5","vue": "^3.0.0"},
  • 按需引入

在需要使用axios的组件中引入 import axios from "axios";

集成jsencrypt

此时, package.json 的依赖变为:

  "dependencies": {"axios": "^0.21.1","core-js": "^3.6.5","jsencrypt": "^3.2.1","vue": "^3.0.0"},
  • 按需引入

在需要使用JSEncrypt的组件中引入 import JSEncrypt from "jsencrypt";

最终的前端登录组件代码

<template><div><span>用户名</span><input type="text" v-model="user.username" /><span>密码</span><input type="text" v-model="user.password" /><input type="submit" v-on:click="login" value="登录" /></div>
</template>
<script>
import { defineComponent } from "vue";
import axios from "axios";
import JSEncrypt from "jsencrypt";export default defineComponent({name: "RSADemo",setup() {},data() {return {user: { username: "dev", password: 123 },publicKey: `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/yhacWdmSauP3/NaDNi1/2l9Z
ZTy+kpRW5OaQcNRErkA1a3APE+IK3axaBGOk9pjUADIrs0Sn2JEtZUMdQvi9B67q
jj4RcAy+68aw0FPvIP7awr/g0KDAGyHLXqoMS0K7zGbNTriaytF6RqeAmu1sX7pP
B1Mp40PX4VyI1s1k4wIDAQAB`,};},mounted() {this.login();},methods: {login: function () {let userinfo = {username: this.encrypt(this.user.username),password: this.encrypt(this.user.password),};axios.post("http://localhost:8000/auth/login", userinfo).then(function (res) {if (res.status == 200) {console.log(res.data);} else {console.error(res);}},function (res) {console.error(res);});},encrypt: function (str) {let jsEncrypt = new JSEncrypt();// 设置加密公钥,一般通过后端接口获取,这里写在前端代码中jsEncrypt.setPublicKey(this.publicKey);let encrypted = jsEncrypt.encrypt(str.toString());return encrypted;},},
});
</script>

RSA加密传输效果

可能遇到的问题

  • 开发环境跨域

方法一:通过开发环境(生产环境可通过Nginx实现)的代理服务进行请求转发,新建 vue.config.js 文件,内容如下:

module.exports = {devServer: {proxy: {'/api': {target: 'http://localhost:8000/',changeOrigin: true,ws: true,secure: true,pathRewrite: {'^/api': ''}}}}
};

方法二:因为后端服务是我们自己开发的,所以可以在后端进行CORS配置,允许跨域

@Configuration
public class CorsConfig implements WebMvcConfigurer {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowedHeaders("*").allowCredentials(true).exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L);}};}
}

附:代码生成RSA密钥对

当然,除了使用Windows、Linux上的openssl工具生成密钥对之外,我们也可以使用代码来直接生成。

<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.64</version>
</dependency>
public class RSAEncrypt {private static final KeyPair keyPair = genKeyPair() ;private static org.bouncycastle.jce.provider.BouncyCastleProvider bouncyCastleProvider = null;public static synchronized org.bouncycastle.jce.provider.BouncyCastleProvider getInstance() {if (bouncyCastleProvider == null) {bouncyCastleProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider();}return bouncyCastleProvider;}/*** 随机生成密钥对*/public static KeyPair  genKeyPair()  {try {//            Provider provider =new org.bouncycastle.jce.provider.BouncyCastleProvider();
//            Security.addProvider(DEFAULT_PROVIDER);SecureRandom random = new SecureRandom();KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", getInstance());generator.initialize(1024,random);return generator.generateKeyPair();} catch(Exception e) {throw new RuntimeException(e);}}/*** 获取公钥字符串(base64字符串)* @return*/public static String generateBase64PublicKey() {PublicKey  publicKey = (RSAPublicKey) keyPair.getPublic();return new String(Base64.encodeBase64(publicKey.getEncoded()));}/*** 获取私钥字符串(base64字符串)* @return*/public static String generateBase64PrivateKey() {PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();// 得到私钥字符串return new String(Base64.encodeBase64((privateKey.getEncoded())));}...
}

Reference

  • Source Code: Github
  • https://blog.csdn.net/aexlinda/article/details/37693167

If you have any questions or any bugs are found, please feel free to contact me.

Your comments and suggestions are welcome!

14-SpringSecurity:前后端分离项目中用户名与密码通过RSA加密传输相关推荐

  1. B站云E办Vue+SpringBoot前后端分离项目——MVC三层架构搭建后台项目

    本项目来源B站云E办,笔记整理了项目搭建的过程和涉及的知识点.对于学习来说,不是复制粘贴代码即可,要知其然知其所以然.希望我的笔记能为大家提供思路,也欢迎各位伙伴的指正. 项目前端学习笔记目录 B站云 ...

  2. Nginx+uwsgi+celery+supervisor部署Django前后端分离项目

    转载 Nginx+uwsgi+celery+supervisor部署Django前后端分离项目 ljmict 0人评论 3887人阅读 2018-08-08 01:29:45 本实验实现了负载均衡.反 ...

  3. SpringSecurity前后端分离(包含token和验证码登录)

    SpringSecurity前后端分离 从上至下操作,直接上手SpringSecurity 文章目录 SpringSecurity前后端分离 1.项目环境 maven依赖 数据库表 2.自定义User ...

  4. 前后端分离项目,后端是如何处理前端传递的token?

    前后端分离项目中,在不使用 SpringSecurity.Shiro 安全框架的情况下,后端是如何处理前段传递的 token 的呢? 简单说一个场景,在一个非常小的项目中,由于业务逻辑比较简单,也没有 ...

  5. 前后端分离项目如何部署_前后端分离项目,如何解决跨域问题?

    跨域资源共享(CORS)是前后端分离项目很常见的问题,本文主要介绍当SpringBoot应用整合SpringSecurity以后如何解决该问题. 01 什么是跨域问题? CORS全称Cross-Ori ...

  6. 在Docker 上完成对Springboot+Mysql+Redis的前后端分离项目的部署(全流程,全截图)

    本文章全部阅读大约2小时,包含一个完整的springboot + vue +mysql+redis前后端分离项目的部署在docker上的全流程,比较复杂,请做好心理准备,遇到问题可留言或则私信 目录 ...

  7. 前后端分离项目部署(部署在同一台服务器)

    前后端分离项目部署(部署在同一台服务器) 博主现在参与的项目是前后端分离的,前端是用vue写的并用npm构建的,后端是用java写的用maven构建的,但是前端和后端在同一个项目中,之前的部署方式是前 ...

  8. Vue2+Node.js前后端分离项目部署到云服务器

    本文参考教程: NodeJS项目部署到阿里云ECS服务器全程详解 - 知乎本文详细介绍如何部署NodeJS项目到阿里云ECS上,以及本人在部署过程中所遇到的问题.坑点和解决办法,可以说是全网最全最详细 ...

  9. 推荐 6 个前后端分离项目

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 前后端分离是现在主流的架构设 ...

最新文章

  1. 【逆向工具】IDA使用5-( string、图形化与视图的切换、图形化显示反汇编地址、自动注释、标签使用)...
  2. android listview 禁止滚动
  3. NYOJ 5767 装背包
  4. ironpython3发布时间_.NET 基金会项目介绍-DLR/IronPython2/IronPython3
  5. SOJ 2800_三角形
  6. 信息学奥赛一本通(1260:【例9.4】拦截导弹(Noip1999))
  7. VS Code Python 将支持 Jupyter Notebook
  8. 授权函php还是提示没权限_大快人心,只需一招,BAT 毒瘤再也不敢滥用权限
  9. 在Vue项目中使用Echarts的一种方式
  10. 34款管理系统、ERP、CRM、OA等(冠唐\金蝶等)
  11. linux i2c驱动协议
  12. 基于SSM框架的BS微博系统的设计与实现
  13. JAVASCRIPT 上传文件的几种方式
  14. 多项logistic回归系数解释_Logistic回归结果的回归系数和OR值解读
  15. 切换阿里巴巴开源镜像站镜像——Kali镜像
  16. 神界计算机丢失msvcp120.dll,Win7系统msvcp120.dll丢失的解决方法
  17. springBoot项目中yml文件${REDIS_HOST:127.0.0.1}写法解析
  18. linux申请令牌错误,解决“请求中包含的安全令牌已过期”错误
  19. 我喜欢计算机科学作文,我爱电脑作文9篇
  20. 水溶性CdSe/ZnS量子点(表面由亲水配体包裹的核/壳型荧光纳米材料)

热门文章

  1. The Path to Learning WR Python FPE.6
  2. python员工管理系统
  3. 杨广中院士莅临我院参观指导
  4. AirMagic for mac(ps/lr无人机航拍效果增强软件) v1.0.2(7263)中文版
  5. SPARK-SQL 读取 内存table 或 hive中的table
  6. 产业、生态、渠道,三个关键词解码2020年科大讯飞开发者节刘庆峰发言
  7. python 批量移动文件_[Python] 自动化办公 批量命名和移动文件
  8. 编译和链接(compile and link)
  9. React如何快速上手
  10. 【微服务|Spring Security⑱】spring security授权汇总