简单了解RSA非对称加密
https://www.veritas.com/zh/cn/information-center/rsa-encryption
目前,存在两种主要类型的加密:
- 对称加密使用相同的密钥来加密和解密数据,例如高级加密标准 (AES)
- 非对称加密也称为公钥加密,因为它需要一对密钥,公钥用于加密,私钥用于解密。Rivest Shamir Adleman 算法就是一个常见的例子。
RSA 之所以有效,是因为随机选择的足够长度的加密密钥几乎是坚不可摧的。
它也称为非对称算法,其中发送者和接收者使用不同的密钥来加密和解密数据。非对称算法为每个发送方分配一对密钥:
- 用于加密的公用密钥
- 用于解密数据的私人密钥
尽管这两个密钥是链接的,但无法从公共密钥导出私有密钥或使用公共密钥解密数据。顾名思义,公钥是众所周知的,但私钥是秘密的,只有拥有它们的用户才能使用。简而言之,每个人都可以使用其公钥向用户发送消息,但只有预期的接收者可以使用其私钥解密消息。
Nest中实现前后端交互数据的RSA加密
步骤
- 首先生成公钥和私钥
- 公钥存放在前端
- 私钥存放在后端
- 前端传递敏感信息的时候通过公钥进行加密
- 后端通过私钥进行解密
生成公钥私钥
你可以通过命令或者一些在线网站都可以!
openssl genpkey -aes256 -algorithm RSA \
-out encrypted_private.pem \
-pkeyopt rsa_keygen_bits:4096
genpkey
OpenSSL 的通用密钥生成命令(替代旧的genrsa)-aes256- 使用 AES-256 算法加密私钥文件
- 执行命令后会提示输入密码(用于加密私钥)
- 后续使用该私钥时需要提供此密码
-algorithm RSA
指定生成 RSA 密钥对-out encrypted_private.pem
输出文件名(PEM 格式)-pkeyopt rsa_keygen_bits:4096
密钥选项:设置 RSA 密钥长度为 4096 位 [现代推荐至少 2048 位(3072/4096 位更安全)]
ok. 我们在本地的项目生成一下看看
➜ nest-project git:(rbac-nest) ✗ openssl genpkey -aes256 -algorithm RSA \
-out encrypted_private.pem \
-pkeyopt rsa_keygen_bits:4096
..+...+.........+............+.+........+.+......+..+..........+..+...+............+......+.......+......+...........+++++++++++++++++++++++++++++++++++++++++++++*.+......+.....+.........+....+...+........+............+...+....+...............+..+......+...+.+......+...+.....+.+...+.................+...+......+....+.....+...+....+......+...+..............+.+++++++++++++++++++++++++++++++++++++++++++++*.....+.+.........+...+.....+...+.+...+..+....+............+............+..+...+...............+...+.........................+.....+...+....+...........+........................+...+............+...+.............+..+.........+..........+.....+....+..+.........+.........+....+...............+..+......+.......+......+............+...+...........+.+.........+.....+.+...+............+..+..........+......+..........................+......+....+...+............+........+.+.................+...............+...+.+...+++++
....+.................+......+....+......+++++++++++++++++++++++++++++++++++++++++++++*.............+++++++++++++++++++++++++++++++++++++++++++++*...+.........+.....+.......+......+..............+.+........+....+........+...................+.........+..+.......+...+...............+...............+..............+......+....+..............+....+....................+.+..+...+.........+..........+...+...+...........+......+...+...................+......+..+............+.+.....................+.........+..+.........+.+........+......+.............+.....+......+........................+.+.........+.....+.......+.........+......+........................+....................+.+......+............+.....+...+.........+...................+..+....+.....+..........+...........+.......+........+...+.+...+......+...........+...+......................+...........+....+...............+......+..+.+..............+................+.........+....................+......+.+...+...+...+.....+.+........................+........................+...+.....+...+......+.+...+......+...+..+...+...+...............+...+....+...+...+.....+......+...+.............+..+............+.+........+..............................+.........+......+.......+..............+.+.....+.........+........................+......+...+...+.........+...+.......+...+...+..+.......+........+......+......+....+..+.......+..+......+.........+....+...............+...........+....+.........+......+..+....+...+......+............+...+..+...+.......+........+.+..........................+..................+.+........+.+++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
PEM pass phrase 是 test
使用私钥提取公钥
openssl rsa -pubout -in encrypted_private.pem -out public_key.pem
| 参数 | 说明 |
|---|---|
rsa |
RSA 密钥处理命令 |
-pubout |
要求输出公钥(默认输出私钥) |
-in private_key.pem |
输入的私钥文件 |
-out public_key.pem |
输出的公钥文件路径 |
nest-project git:(rbac-nest) ✗ openssl rsa -pubout -in encrypted_private.pem -out public_key.pem
Enter pass phrase for encrypted_private.pem: # 输入刚刚设置的 PEM pass phrase : test
writing RSA key
➜ nest-project git:(rbac-nest) ✗
就可以看到当前执行目录下生成了公钥文件!
业务场景:用户的注册和登录
创建项目什么的就不在这讲了(什么初始化项目,数据库配置)
我这里使用的是 Typeorm + Mysql
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
import { User } from './user/entities/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '123',
database: 'rsa-test',
synchronize: true,
logging: true,
entities: [User],
poolSize: 10,
connectorPackage: 'mysql2',
extra: {
authPlugin: 'sha256_password',
},
}),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
生成用户 resource
nest g resource user
UserEntity
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 50,
})
username: string;
@Column({
length: 100,
})
password: string;
@CreateDateColumn()
createTime: Date;
@UpdateDateColumn()
updateTime: Date;
}
用户 CreateUserDto, 简单,只要用户密码
export class CreateUserDto {
username: string;
password: string;
}
创建用户的 controller 没啥逻辑,就调用就完了!一切从简,也不做什么字段校验了!
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
首先我们先来实现 注册的 Service
注册的用户入库前,密码也是要做加密的!
node 项目一切加密相关的可以用 bcrypt 这个包
npm i bcrypt
added 3 packages, removed 14 packages, and audited 768 packages in 5s
133 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
还有对应类型的包
npm i @types/bcrypt
added 1 package, and audited 769 packages in 2s
133 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
UserService 实现
// 创建用户
async create(createUserDto: CreateUserDto) {
// 1.首先看数据库里有没有 username 已经存在的
const findByUsernameRet = await this.userRepository.findOne({
where: {
username: createUserDto.username,
},
});
if (findByUsernameRet) {
return '用户已经注册过了';
}
// 2. 没有则创建用户
const newUser = new User();
newUser.username = createUserDto.username;
newUser.password = await bcrypt.hash(createUserDto.password, 10);
return this.userRepository.save(newUser);
}
测试一下
curl --location --request POST 'http://localhost:3000/user' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--header 'Host: localhost:3000' \
--header 'Connection: keep-alive' \
--data-raw '{
"username":"nor_user",
"password":"123"
}'
| id | username | password | createTime | updateTime |
|---|---|---|---|---|
| 1 | nor_user | $2b$10$71pv1UE9e9F3.lS5AQK88.OvUlRK7cpOtTcVPWT7mRRtEXw3lGQfm | 2025-06-29 03:07:40.372160 | 2025-06-29 03:07:40.372160 |
这一步其实还没用到我们的 RSA ,因为还没前端我们设计的是前端的传输都是通过 RSA 加密后的信息
简单的实现下前端先!
<main class="max-w-4xl mx-auto px-4 pt-16 pb-12">
<div
class="bg-white/70 backdrop-blur-sm rounded-[2.5rem] shadow-2xl p-12"
>
<div class="grid">
<div>
<div class="mb-10 text-center">
<img
src="https://unpkg.com/lucide-static@latest/icons/lock.svg"
class="mx-auto w-12 h-12 p-2 bg-primary-100 rounded-xl mb-4"
/>
<h2 class="text-2xl font-bold text-gray-800">账户登录</h2>
</div>
<div class="space-y-6">
<!-- 输入框组 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>电子邮箱</label
>
<div class="relative">
<input
type="username"
v-model="loginForm.username"
class="w-full outline-none bg-white/80 backdrop-blur-sm border-2 border-gray-200/50 rounded-xl py-3 px-4 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 transition"
placeholder="name@example.com"
/>
<img
src="https://unpkg.com/lucide-static@latest/icons/mail.svg"
class="absolute right-4 top-3 w-5 h-5 text-gray-400"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2"
>密码</label
>
<div class="relative">
<input
type="password"
v-model="loginForm.password"
class="w-full outline-none bg-white/80 backdrop-blur-sm border-2 border-gray-200/50 rounded-xl py-3 px-4 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 transition"
placeholder="••••••••"
/>
<img
src="https://unpkg.com/lucide-static@latest/icons/lock.svg"
class="absolute right-4 top-3 w-5 h-5 text-gray-400"
/>
</div>
</div>
<div class="flex justify-center gap-10">
<n-button color="#8a2be2" @click="loginAction">
<template #icon>
<n-icon>
<LogIn />
</n-icon>
</template>
登录
</n-button>
<n-button color="#f00" @click="registerAction">
<template #icon>
<n-icon>
<AddOutline />
</n-icon>
</template>
立即注册
</n-button>
</div>
</div>
</div>
</div>
</div>
</main>

registerAction 这个方法的实现
// 点击注册
const registerAction = async () => {
const loginParams = Object.assign({}, loginForm);
loginParams.password = encrypted(loginParams.password);
const res = await userRegister(loginParams);
message.info(res.data);
};
我们需要把密码通过 encrypted 方法(内部实现 RSA 加密)处理后,传给后端
下面 encrypted 函数就是使用生成的公钥进行加密具体实现
npm install node-forge --save
npm install @types/node-forge --save-dev
这里我们使用 node-forge 这个库在前端处理 RSA 加密
import forge from 'node-forge';
// 公钥内容
const publicKey = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnZZFfQ5s2Qk1XLhRqXwG
+Oo9jwfqrr+t+48aDmBh8sDlG6Fl5k6iVgsRJ7o6cURQnqZCVaoVs2lGDlN87Qak
lH3VzlkfwwBbFeAOQA2eav+BgaT9u1P46MYB1li4iUdSyrCOcSOob8aUp3lZ14N3
OpwHtPk+IL0WIRzhwVnlDF8Xr8/gLvz+D71mVBdaNm1Zw2vq8K6PJzVV8z+7CFJo
ZNuShCpNhVYafxSeblg6OPObQnzWCa0eCb96v6GsiID0bKjX2hKW8cekjl1xWhd+
kmH/tvtkRTcRMUdWR+yAkzAIox3+ZpWrwas2hcIgBxuFA0miicd2Ek6gk0f6kSyr
iwli2daeJ645mUDMvusCR/c6UHVqUQ6g9cFmksj7RzTAHAwjrq6y35/Q/vrgv6Q3
QUND9xLuSx9eG/2zmf+47MZiaE3am7teShZOLsH4r421/+n8q38z1uJPcQuqVHjy
7lOVmYkOuSpANoaeu6FIMUAeo9lmRSubN9ZIqK8iapC41oaKlrTw/JgViu1eabz0
tGfHF+GZgvohq7PDza741hwnbBGTFeV1vdFj7AUgqjmXMVkGQ48PC8kg2xInio7F
p6X/JQ8nEMa5F740lzdeppcIYsMBcBisMZnpwwXkiyOmXQV1PK1ICTH/IKSuI7ud
4YKymGrw6KfO8ZYavPnXbzECAwEAAQ==
-----END PUBLIC KEY-----`;
/**
* 使用OAEP填充模式加密字符串
* @param str 要加密的字符串
* @returns 加密后的base64字符串
*/
export const encrypted = (str: string) => {
try {
// 需要安装node-forge库
// npm install node-forge --save
// npm install @types/node-forge --save-dev
const publicKeyForge = forge.pki.publicKeyFromPem(publicKey);
const encrypted = publicKeyForge.encrypt(str, 'RSA-OAEP', {
md: forge.md.sha1.create(),
mgf1: {
md: forge.md.sha1.create(),
},
});
// 转换为base64格式
const base64Result = forge.util.encode64(encrypted);
return base64Result;
} catch (e) {
console.error('加密失败:', e);
return '';
}
};
我们试一下发起注册请求看看是什么样子
curl 'http://localhost:5173/api/user' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsInVzZXJuYW1lIjoiY2h0ZXN0NSIsImlhdCI6MTc0MjYwOTkyNCwiZXhwIjoxNzQyNjk2MzI0fQ.kE6y2wItnRyXNWYWfXzzSQq2SNrP_xpi6_23quCpSOI' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json;charset=UTF-8' \
-b 'session=.eJy9kdtum0AQht-F61DtLiyG3sXHYAWccDTbVtEewAYW4sRYsany7l3cg5S0aaWq6sUuO8PMfDPzf9Ye72W-195_-KzR3e6uFNr78wMAqF2cXy1tcuX8eMC5Y348jDCyh19D3p0s990592yJfM8fS_YtmlnWEA2L78Hn2gwjVECAdTsXlm5aAukMCqCDwnBGjmCMIvt7wjfyvjuIvO2054tfcSyMnaE3YL_gcIEZog7VGR8h3bQx0G0OuG4ykRsOLTil9BWnyynf5o9vcUYGHW572AGjUN0mUBNiE78kj2yGLAcCnRU2000E1YQGVrPiAhbMxtwwilfkYc1UNGWrPX8a6K-UwG8rgf9eCWjQEQOs0C0TAd0cQVunkFs6NBHl0IQjVrB_ocSfOfQ1538pAZHxOyU-XWjdfZ23yp-fllu24OWqXLpx70K_dPduG2A-cS233q2TydJ5p4Ia3iQ9VU7SzPccxXu3kVuhbC_i0IsujVVUP_nlU0nbYOdW92XWeJBEY5lFWbdK_do_AUAW8-Y62spV6nZev638amb4KMar6Qa7KldcyScSKnC7lEQdjjbl9WTZi9QdGmxv4_lYnQmDm6No5NRHMsuM-JjLcSZaeVCUWECBkyYoMyMomLFsqCTSX5PWm8BOpDMQxRyliXMTguzI4k0XVRvsR3VPQbLKU7wLYCL5bN7TKlgFKSEkTcIY-TdsTR7COAj8NBmnMTG96fw2vKq7yJiZERK3UUP61XoHqZzfeNCn_my7EGtxXKPjTThRS21wScqf95UZ_mnwu1UGvT6oyNQF12lSen3d-QsXeSdYkugSXEc1JI1fZZV38qbbyguHmonJf2gwQ37lnWuyRlYUOocshXLQYn2lJujj49fvfLBV_5eDDUk1b9wWvHvA49Ta3vZ9tdwlD57ZTtrwrt4AJ4wXBm6w2ItNXxRXvL_Xnr8AdPK1MQ.aF9b4g.CNZEX3pNToMVlJnMGtL0rDXTpes' \
-H 'Origin: http://localhost:5173' \
-H 'Referer: http://localhost:5173/login' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--data-raw '{"username":"i933310737@qq.com","password":"FlY6/9gQDGbmbkinWJ12o7IFnv+RmlzCVI5SPVo5h62AeKmcJS0QSUFnah4LTDfPmyNIysGBfA1Y/qd3l0l/BYf8CNxeqLBS+eGVNxP2Iauh+2uYYSpgXbPIm8GxJCbJ6aeIre1lb9OhyRMNkcxOqOcoUrdXZJY3O6Uzr/7ShwbBa166DTyG4YcIFGzeI7gFslJSye7pIQhfvXlLFh6s3271GRu70GqPmw8YyWMC2ITKKp4UVmgX7ZnVWvlHJ0FJmnG+VOVIrykF3ULZSaTNPb/P7ETEHLC1RsJt/PVEcmqMeBvU7VY+GCUNvVtEV8Qq20jksHge0kI5Qgcj2qz3DLHwmHXzyBPnbw57FYb3vQTmtMwv0K18iJ7E5xyVLMWn2VF63w0intoR5k1eRArldOMiONBMdtRkVVGfctn9E4x3a7Gxsa9x38HcXVmr26Pdgs4m0YZEttqXCg2WyoDqUIMjR8st9/as02LG7q269lrLlvhllZdhY8d6BP0jcZZDzX3x2b+W+KfVxDnAGIUVM3Y368qKCTioZtT+EkxY1ccYEcOqrFQ7UBpnYyY3s34vYKi5/0iDpcRQcI5k/2pFCLSP/5TjS2TxfZNw4FZX+JrvsJeU5fQtRf64QqnHMnGG/Stpvd9fhjurSSXL20prSkMsXBfgUIeVD/eH6zYmLY8="}'
可以看到前端的 password 字段已经被加密了
后端私钥解密为明文:
上面我们实现的注册,是直接把前端明文传过来的 password 进行 hash 加密,因为前端传输加了 RSA 的加密过程,所以我们接收到password 后需要先进行 RSA 解密为明文后,再hash 加密入库
生成 RSA module 和 Service
nest g module rsa
CREATE rsa/rsa.module.ts (80 bytes)
nest g service rsa
CREATE rsa/rsa.service.spec.ts (439 bytes)
CREATE rsa/rsa.service.ts (87 bytes)
UPDATE rsa/rsa.module.ts (151 bytes)
我们先把 公钥私钥放在 assets [这里记得打包 nest 项目也要把 asset 的内部打包进去,因为只会打包 src 目录下的内容]


RsaServuce 的实现
import { Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
import { readFileSync } from 'fs';
import * as path from 'path';
@Injectable()
export class RsaService {
private privateKey: string;
constructor() {
// 读取私钥
const privateKeyPath = path.join(
process.cwd(),
'assets/encrypted_private.pem',
);
this.privateKey = readFileSync(privateKeyPath, 'utf-8');
console.log('privateKey', this.privateKey);
}
//decrypt 解密函数
decrypt(encryptedText: string): string {
console.log({ encryptedText });
const buffer = Buffer.from(encryptedText, 'base64');
const decrypted = crypto.privateDecrypt(
{
key: this.privateKey,
passphrase: 'test', // 这是我们通过 openssl 生成私钥的时候设置的
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, // 使用 PKCS#1 填充
},
buffer,
);
return decrypted.toString('utf8');
}
}
RsaModule providers 把 RsaServcie 暴露给IOC exports 把 RsaServcie 可以给其他模块注入
import { Module } from '@nestjs/common';
import { RsaService } from './rsa.service';
@Module({
providers: [RsaService],
exports: [RsaService],
})
export class RsaModule {}
AppModule 可以不用加,这里没有需要注册 Controller
引入 RsaModule
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { RsaModule } from '../rsa/rsa.module';
@Module({
imports: [TypeOrmModule.forFeature([User]), RsaModule],
controllers: [UserController],
providers: [UserService],
exports: [UserService], // Export UserService to be used in other modules
})
export class UserModule {}
UserService
// 创建用户
async create(createUserDto: CreateUserDto) {
// 1.首先看数据库里有没有 username 已经存在的
const findByUsernameRet = await this.userRepository.findOne({
where: {
username: createUserDto.username,
},
});
if (findByUsernameRet) {
return '用户已经注册过了';
}
// 2. 没有则创建用户
const newUser = new User();
newUser.username = createUserDto.username;
const rsaParsePassword = this.rsaSerivce.decrypt(createUserDto.password);
console.log(rsaParsePassword);
newUser.password = await bcrypt.hash(rsaParsePassword, 10);
return await this.userRepository.save(newUser);
}
注册请求下
curl 'http://localhost:5173/api/user' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsInVzZXJuYW1lIjoiY2h0ZXN0NSIsImlhdCI6MTc0MjYwOTkyNCwiZXhwIjoxNzQyNjk2MzI0fQ.kE6y2wItnRyXNWYWfXzzSQq2SNrP_xpi6_23quCpSOI' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json;charset=UTF-8' \
-b 'session=.eJy9kdtum0AQht-F61DtLiyG3sXHYAWccDTbVtEewAYW4sRYsany7l3cg5S0aaWq6sUuO8PMfDPzf9Ye72W-195_-KzR3e6uFNr78wMAqF2cXy1tcuX8eMC5Y348jDCyh19D3p0s990592yJfM8fS_YtmlnWEA2L78Hn2gwjVECAdTsXlm5aAukMCqCDwnBGjmCMIvt7wjfyvjuIvO2054tfcSyMnaE3YL_gcIEZog7VGR8h3bQx0G0OuG4ykRsOLTil9BWnyynf5o9vcUYGHW572AGjUN0mUBNiE78kj2yGLAcCnRU2000E1YQGVrPiAhbMxtwwilfkYc1UNGWrPX8a6K-UwG8rgf9eCWjQEQOs0C0TAd0cQVunkFs6NBHl0IQjVrB_ocSfOfQ1538pAZHxOyU-XWjdfZ23yp-fllu24OWqXLpx70K_dPduG2A-cS233q2TydJ5p4Ia3iQ9VU7SzPccxXu3kVuhbC_i0IsujVVUP_nlU0nbYOdW92XWeJBEY5lFWbdK_do_AUAW8-Y62spV6nZev638amb4KMar6Qa7KldcyScSKnC7lEQdjjbl9WTZi9QdGmxv4_lYnQmDm6No5NRHMsuM-JjLcSZaeVCUWECBkyYoMyMomLFsqCTSX5PWm8BOpDMQxRyliXMTguzI4k0XVRvsR3VPQbLKU7wLYCL5bN7TKlgFKSEkTcIY-TdsTR7COAj8NBmnMTG96fw2vKq7yJiZERK3UUP61XoHqZzfeNCn_my7EGtxXKPjTThRS21wScqf95UZ_mnwu1UGvT6oyNQF12lSen3d-QsXeSdYkugSXEc1JI1fZZV38qbbyguHmonJf2gwQ37lnWuyRlYUOocshXLQYn2lJujj49fvfLBV_5eDDUk1b9wWvHvA49Ta3vZ9tdwlD57ZTtrwrt4AJ4wXBm6w2ItNXxRXvL_Xnr8AdPK1MQ.aF9b4g.CNZEX3pNToMVlJnMGtL0rDXTpes' \
-H 'Origin: http://localhost:5173' \
-H 'Referer: http://localhost:5173/login' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--data-raw '{"username":"i933310737@qq.com","password":"kZm4IWzbpJAllrI9bq721F8QiaYgvnqlqfOoUeen3LRoIxGWmEOmYXfIirbIV1/rkO6bRyKcCV0EEq8EHvlvw4B5LNDO8kbu2gg/l9yhytckMHeMkoz7wa1FyNNbpDxUnm7+ZCVM9H8W+f21F40ZxgA+hLilWo8tmt3euDPEy1yfeNl4PRBWH0uTaRylOYKvq8frfZsSP4WuxKoS4t5ED4y7r7u8jC8/olQgLqGh6p0zGsgv/eOO1IyvjryDAdvt6StT86QlqsepxHlor5slAzk6nej5tChBj2hYJnKn9M0O8pqRuKySnWSM0fivf754qW1nI5pkYJOWqid76DDeFtOwcpaxyrh8JtKP+duaD/tnHszOVSTJIm+dwGRtM4mSJ/tVU4AUvxnd9tWGkbv/PiyH5/uqBw7TI5U2BpFPhr0kFUZRwjMJ3Kzyhda4U2zmI/fcwuHbodCqZqg69p5L9AmMRPczSX+zPe/uj1eIzplS53PmAvs5LQ+K1zYhLdnFR81CV4Iw0T2OJAhnSQe7x49cgkOatdvLRt32OrhZLQS7+xAOUxdZeqfAm+wrtsggMmeFud2G/BIydF0WdHXWhOFXLULK/9A2gYXSIigJeK1wycvmRJrsN3ItCg8OJTwjotrS6rrQfrB5Rv2pRHamcP3hcYFoQG3EfvPn7OKvXuY="}'
可以看到请求解析的已经成功了!

后面就按照之前的正常加密入库就可以了
实现登录
后端部分
LoginUserDto
import { IsNotEmpty, Length } from 'class-validator';
export class LoginUserDto {
@IsNotEmpty()
@Length(1, 20)
username: string;
@IsNotEmpty()
@Length(1, 1000)
password: string;
}
Login Handler
@Post('/login')
async login(@Body() loginUserDto: LoginUserDto) {
return this.userService.login(loginUserDto);
}
login Service
// 用户登录
async login(loginUserDto: CreateUserDto) {
const findByUsernameRet = await this.userRepository.findOne({
where: {
username: loginUserDto.username,
},
});
if (!findByUsernameRet) {
return '用户不存在';
}
const rsaParsePassword = this.rsaSerivce.decrypt(loginUserDto.password);
console.log(rsaParsePassword);
const isMatch = await bcrypt.compare(
rsaParsePassword,
findByUsernameRet.password,
);
if (!isMatch) {
return '账号或密码错误';
}
return '用户登录成功';
}
前端部分
// userLogin 用户登录
export const userLogin = (data: any) => fetchPost('/user/login', data);
// 用户登录方法
const loginAction = async () => {
const loginParams = Object.assign({}, loginForm);
loginParams.password = encrypted(loginParams.password);
const res = await userLogin(loginParams);
message.info(res.data);
};
登录接口实现
测试一下 我们刚刚注册的账号是 i933310737@qq.com/eerr123ss

那这里就完成了 前后端的数据通过 RSA 进行了加密的过程!
流程:
- 客户端用 RSA 公钥加密密码
- 服务器用 RSA 私钥解密
- 服务器用 bcrypt 哈希解密后的密码并存储
其他的注意点
- 定期更换私钥密码是重要的安全实践
评论区