粥里有勺糖

vuePress-theme-reco 粥里有勺糖    2018 - 2023
粥里有勺糖 粥里有勺糖

Choose mode

  • dark
  • auto
  • light
关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
author-avatar

粥里有勺糖

285

文章

40

标签

关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
  • devlearn

    • 开发教程
    • 实践:利用ArrayBuffer实现预览指定目录下的所有文件的内容
    • 在linux-deepin上使用deepin-wine5完美运行腾讯会议/QQ/微信等此类应用
    • eslint插件开发教程
    • ServerLess之云函数实践-天气API
    • 移动端阻止弹窗下层页面被滑动
    • 小技巧:for of中获取index
    • Git常用的一些基本操作
    • 向页面注入js实现为图片和文字元素添加透明蒙层
    • 实践:使用jsencrypt配合axios实现非对称加密传输的数据
    • 封装dotenv库实现类似Vite加载环境变量的行为
    • 30行代码实现合并指定目录下的所有文件的内容
    • 马上中秋了,把鼠标指针变为小玉兔
    • Node中require与fs.readFile读取JSON文件的对比
    • 使用免费的七牛云OSS(10G)搭建个人的在线图床
    • 分享封装的一些七牛云OSS操作方法
    • 本地配置SSH免密远程登录服务器
    • 工具方法汇总
    • 腾讯云Serverless实践-Node.js服务部署
    • 腾讯云Serverless实践-静态网站托管
    • 为什么'\x1B'.length === 1?\x与\u知识延伸
    • Vite插件开发纪实:vite-plugin-monitor(上)
    • Vite插件开发纪实:vite-plugin-monitor(中)
    • Vite插件开发纪实:vite-plugin-monitor(下)
    • 解决Vite-React项目中js使用jsx语法报错的问题
    • webpack 项目接入Vite的通用方案介绍
    • webpack 项目接入Vite的通用方案介绍-草稿
    • 优雅的处理挂载window上的函数可能不存在的情况
    • Mac上抓包秒通关羊了个羊

实践:使用jsencrypt配合axios实现非对称加密传输的数据

vuePress-theme-reco 粥里有勺糖    2018 - 2023

实践:使用jsencrypt配合axios实现非对称加密传输的数据

粥里有勺糖 2021-06-08 技术笔记技术教程

# 实践:使用jsencrypt配合axios实现非对称加密传输的数据

# 背景

不希望应用发送的数据能在 Devtools 中被看到,避免接口被“同行”扒下来,然后被恶意使用

图片

要避免此问题,首先想到的就是对传输的数据进行一次加密,让后端自行解密然后处理

尽管js源码是被浏览器公开的,但通过构建工具混淆后,在没有source map的情况下还不不易定位目标代码

期望加密后的样子传输的内容如下

图片

# 加密方案简述

# 对称加密

对称加密就是两边拥有相同的秘钥,两边都知道如何将密文加密解密。

这种加密方式固然很好,但是问题就在于如何让双方知道秘钥。

由于传输数据都是走的网络,如果将秘钥通过网络的方式传递的话,一旦秘钥被截获就没有加密的意义

# 非对称加密

有公钥私钥之分:

  • 公钥所有人都可以知道,可以将数据用公钥加密,但是将数据解密必须使用私钥解密
  • 私钥只有分发放公钥的一方才知道

这种加密方式就可以完美解决对称加密存在的问题

通过对比,选用保密性好的 非对称加密 方案作为加密方案

本文选用 RSA (opens new window) 对称加密算法

# 公私钥生成

根据百度经验的建议,生成一个1024位的的秘钥

这里使用openssl生成,window下建议在Git Bash下使用

私钥

openssl genrsa -out rsa_1024_priv.pem 1024
1
查看生成私钥
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQC0YiWffUFPnr2OyfcaM8QLkdq30VQxjIctZJX/CnYn7NorwCyK
iBX3hULSUrBxBACZBtfeGtpT+I9yn3+6s4kClZiJNOOP8bvJab4fboLUtJt9XciK
ENRxHVcnrYMrM+0diXGH/COjeA7ym/L2M/eX1fxpZjTByaJdx0FZAlrD4wIDAQAB
AoGBAKarxU2vw4gZCdeE3/BzAmMaWrjcD2pVCZY0ya/Fb9WGMTSZtc4u3fU+Sbbi
tqtGYnMC8rUDpNZP5eOoYrIVL7MJFj7n5DIT6TJOxWRavWsei812pQlc6ccR/VEH
o0nN5JQRZH8DC+CE86QddOW7VfnRMIP4A7j8UEEiRxsPITExAkEA4N10W/HA7lYE
WKSRES4/gAJHg7cqS2dAWIovX/JtxzUyK2q50rWjbgbXXOGriIp3cRJeH2Jmi6tm
jQQ+ldWAqwJBAM1b/9cRLWJ6fPdDIjbrc9IQE6C5HYI0FsHuyCn3zUKnD1+hc7k5
+BrSpyG5VzdPH3WOhm6GJ5TCf/LlxvvIeakCQQCEO1Ywt2KYBTc7FVNFgifPVAfP
+gdCHi6lomUni/1oVuzwwSsTMMMxcY51zTM88Qg6Eu4MkKXy3lFI/cT8AXhPAkEA
vy4q27muGsQVmsvxClfgl2tIGpS7l/+OQDVgO1Hq0WZdtZXE+mexRqdd2NOHEoKi
svpgxHw4VRFNtH+d48EbIQJBAMcLRGOuUrPhyYXmvEvvGGqcFH7zwjoyYlQQYR3a
IeAC4Mo/vDo7agLXvlRFXgPWMXAJqzjVhZ+xmew4hUqe5HY=
-----END RSA PRIVATE KEY-----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

公钥

openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem
1
查看生成的公钥
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YiWffUFPnr2OyfcaM8QLkdq3
0VQxjIctZJX/CnYn7NorwCyKiBX3hULSUrBxBACZBtfeGtpT+I9yn3+6s4kClZiJ
NOOP8bvJab4fboLUtJt9XciKENRxHVcnrYMrM+0diXGH/COjeA7ym/L2M/eX1fxp
ZjTByaJdx0FZAlrD4wIDAQAB
-----END PUBLIC KEY-----

1
2
3
4
5
6
7

# jsencrypt

  • jsencrypt (opens new window)
  • nodejs-jsencrypt (opens new window)

使用 Javascript 进行RSA加密的解决方案

# 使用

安装依赖

# web
npm i jsencrypt

# node
npm i nodejs-jsencrypt
1
2
3
4
5

引入

// web
import JSEncrypt from 'jsencrypt'

// node
const { JSEncrypt } = require('nodejs-jsencrypt')
1
2
3
4
5

# 公钥加密方法

// 上述自动生成
const pubKey = '上述生成的公钥'

function publicEncrypt(str){
    const encrypt = new JSEncrypt()
    encrypt.setPublicKey(pubKey)
    return encrypt.encrypt(str)
}
1
2
3
4
5
6
7
8

# 私钥解密方法

const privKey = `上述生成的私钥`

function privDecrypt(str) {
    const encrypt = new JSEncrypt()
    encrypt.setPrivateKey(privKey)
    return encrypt.decrypt(str)
}
1
2
3
4
5
6
7

可以看出API非常简洁

# 使用示例

let str = publicEncrypt('hello world')
console.log(str)
console.log(privDecrypt(str))
1
2
3

# 结合Axios实践

# Axios配置

npm i axios
1

将加密逻辑放入到axios的请求拦截器中,将原内容使用 JSON.stringify处理后再进行加密,加密后的内容使用value属性传递,如下所示

import axios from "axios";

// 引入刚刚编写的加密方法
import { publicEncrypt } from "./utils/crypto";

const http = axios;
http.defaults.baseURL = '/api'
http.defaults.headers = {
  "content-Type": "application/json"
};

// 请求拦截器
http.interceptors.request.use(
  config => {
    // 发送之前操作config
    // 对传递的 data 进行加密
    config.data = {
      value:publicEncrypt(JSON.stringify(config.data))
    }
    return config;
  },
  err => {
    // 处理错误
    return Promise.reject(err);
  }
);
http.interceptors.response.use(
  response => {
    // 返回前操作
    return response.data;
  },
  err => {
    return Promise.reject(err);
  }
);

export default http;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 服务端解密示例代码

这里列举了两种,一种直接使用Node.js的http模块编写,一种使用Express编写:

  1. 解密收到的内容
  2. 将解密后的内容直接返回

# http模块示例

使用data事件与end事件配合,接收传递的数据,然后进行解密返回

const http = require('http')

// 引入解密方法
const { privDecrypt } = require('./utils/crypto')

const server = http.createServer((req, res) => {
    res.setHeader('content-type','application/json')
    let buffer = Buffer.alloc(0)

    // 接收传递的数据
    req.on('data',(chunk)=>{
        buffer = Buffer.concat([buffer, chunk])
    })
    req.on('end',()=>{
        try {
            // 解密传递的数据
            const data = privDecrypt(JSON.parse(buffer.toString('utf-8')).value)
            res.end(data)
        } catch (error) {
            console.log(error);
            res.end('error')            
        }
    })
})

// 启动
server.listen(3000, err => {
    console.log(`listen 3000 success`);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Express示例

配置一个前置的*路由,解密传递的内容,然后将其重新绑定到req.body上,供后续其它路由使用

const express = require('express')
const { privDecrypt } = require('./utils/crypto')

const server = express()

server.use(express.urlencoded({ extended: false }))
server.use(express.json({ strict: true }))

// 首先进入的路由
server.route('*').all((req, res, next) => {
    console.log(`${req.method}--${req.url}`)
    req.body = JSON.parse(privDecrypt(req.body.value))
    next()
})

server.post('/test/demo',(req,res)=>{
    // 直接返回实际的内容
    res.json(req.body)
})

// 启动
server.listen(3000, err => {
    console.log(`listen 3000 success`);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 前端代码示例

使用了 Vite 作为开发预览工具

vite.config.js配置: 只做了请求代理,解决开发跨域问题

export default {
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            },
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

页面

<body>
    <button id="send">发送</button>
    <hr>
    <h2></h2>
    <textarea id="receive" placeholder="接收的内容"></textarea>
    <script type="module" src="./index.js"></script>
</body>
1
2
3
4
5
6
7

逻辑

import $http from './http'
const $send = document.getElementById('send')
const $receive = document.getElementById('receive')

$send.addEventListener('click',function(){
    // 发送一个随机内容
    $http.post('/test/demo',{
        name:'xm',
        age:~~(Math.random()*1000)
    }).then((res)=>[
        updateReceive(res)
    ])
})

function updateReceive(data){
    $receive.value = data instanceof Object?JSON.stringify(data):data
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 运行结果

页面

图片

发送网络请求

图片

请求响应内容

图片

大工告成,接入十分简单

完整的示例代码仓库 (opens new window)

Edit this page (opens new window)
Last Updated: 2022/5/15 12:46:34