Skip to content

Commit 68058cc

Browse files
committed
【feature】新增加密隧道工具; review by songym
1 parent affe702 commit 68058cc

File tree

9 files changed

+400
-82
lines changed

9 files changed

+400
-82
lines changed

build/webpack.config.base.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ module.exports = {
5858

5959
//其它解决方案配置
6060
resolve: {
61-
extensions: ['.js', '.json', '.css']
61+
extensions: ['.js', '.json', '.css'],
62+
fallback: {
63+
crypto: false,
64+
stream: false,
65+
vm: false
66+
}
6267
},
6368

6469
externals: {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"mapbox-gl": "1.13.2",
154154
"maplibre-gl": "3.1.0",
155155
"mapv": "2.0.62",
156+
"node-forge": "^1.3.1",
156157
"ol": "6.14.1",
157158
"pbf": "3.2.1",
158159
"process": "^0.11.10",

src/common/index.common.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ import {
333333
ArrayStatistic,
334334
getMeterPerMapUnit,
335335
getWrapNum,
336-
conversionDegree
336+
conversionDegree,
337+
EncryptFetchRequestUtil
337338
} from './util';
338339
import { CartoCSS, ThemeStyle } from './style';
339340
import {
@@ -498,6 +499,7 @@ export {
498499
isCORS,
499500
setCORS,
500501
FetchRequest,
502+
EncryptFetchRequestUtil,
501503
ColorsPickerUtil,
502504
ArrayStatistic,
503505
getMeterPerMapUnit,

src/common/namespace.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ import {
337337
isCORS,
338338
setCORS,
339339
FetchRequest,
340+
EncryptFetchRequestUtil,
340341
ColorsPickerUtil,
341342
ArrayStatistic,
342343
CartoCSS,
@@ -493,6 +494,7 @@ SuperMap.isCORS = isCORS;
493494
SuperMap.setRequestTimeout = setRequestTimeout;
494495
SuperMap.getRequestTimeout = getRequestTimeout;
495496
SuperMap.FetchRequest = FetchRequest;
497+
SuperMap.EncryptFetchRequestUtil = EncryptFetchRequestUtil;
496498

497499
// commontypes
498500
SuperMap.inherit = inheritExt;

src/common/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
"echarts": "5.4.3",
1919
"fetch-ie8": "1.5.0",
2020
"fetch-jsonp": "1.1.3",
21+
"flatgeobuf": "3.23.1",
2122
"promise-polyfill": "8.2.3",
2223
"lodash.topairs": "4.3.0",
2324
"lodash.uniqby": "^4.7.0",
2425
"lodash.clonedeep": "^4.5.0",
2526
"mapv": "2.0.62",
26-
"flatgeobuf": "3.23.1",
27+
"node-forge": "^1.3.1",
2728
"rbush": "^2.0.2",
2829
"urijs": "^1.19.11",
2930
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { FetchRequest } from './FetchRequest';
2+
import {
3+
RSAEncrypt,
4+
AESGCMEncrypt,
5+
AESGCMDecrypt,
6+
generateAESRandomKey,
7+
generateAESRandomIV
8+
} from './RSAAndAESEn-DecryptorUtil';
9+
10+
/**
11+
* @private
12+
* @name EncryptFetchRequestUtil
13+
* @namespace
14+
* @category BaseTypes Util
15+
* @classdesc 加密请求地址
16+
*/
17+
export class EncryptFetchRequestUtil {
18+
constructor(serverUrl = 'http://172.16.13.234:8090/iserver') {
19+
this.serverUrl = serverUrl.split('').slice(-1)[0] === '/' ? serverUrl : `${serverUrl}/`;
20+
this.tunnelUrl = undefined;
21+
this.blockedUrlRegex = {
22+
HEAD: [],
23+
POST: [],
24+
GET: [],
25+
PUT: [],
26+
DELETE: []
27+
};
28+
this.encryptAESKey = generateAESRandomKey();
29+
this.encryptAESIV = generateAESRandomIV();
30+
}
31+
32+
async encryptRequest(options) {
33+
const config = Object.assign({ baseURL: '' }, options);
34+
const tunnelUrl = await this.createTunnel();
35+
if (!tunnelUrl) {
36+
return;
37+
}
38+
for (const pattern of this.blockedUrlRegex[config.method.toUpperCase()]) {
39+
const regex = new RegExp(pattern);
40+
if (regex.test(config.baseURL + config.url)) {
41+
const data = {
42+
url: config.baseURL + (config.url.startsWith('/') ? config.url.substring(1, config.url.length) : config.url),
43+
method: config.method,
44+
timeout: config.timeout,
45+
headers: config.headers,
46+
body: config.data
47+
};
48+
// 替换请求
49+
config.method = 'post';
50+
config.data = AESGCMEncrypt(this.encryptAESKey, this.encryptAESIV, JSON.stringify(data));
51+
if (!config.data) {
52+
throw 'encrypt failed';
53+
}
54+
config.url = this.tunnelUrl;
55+
break;
56+
}
57+
}
58+
const response = await FetchRequest.commit(config.method, config.url, config.data, config.options);
59+
if (config.url === this.tunnelUrl) {
60+
const result = await response.text();
61+
const decryptResult = AESGCMDecrypt(this.encryptAESKey, this.encryptAESIV, result);
62+
if (!decryptResult) {
63+
console.debug('解密请求响应失败');
64+
return;
65+
}
66+
return JSON.parse(decryptResult).data;
67+
}
68+
return response;
69+
}
70+
71+
async getRSAPublicKey() {
72+
try {
73+
const response = await FetchRequest.get(`${this.serverUrl}services/security/tunnel/v1/publickey`);
74+
// 解析publicKey
75+
const publicKeyObj = await response.json();
76+
// 生成AES密钥
77+
const aesKeyObj = {
78+
key: this.encryptAESKey,
79+
iv: this.encryptAESIV,
80+
mode: 'GCM',
81+
padding: 'NoPadding'
82+
};
83+
// 将AES密钥使用RSA公钥加密
84+
const aesCipherText = RSAEncrypt(publicKeyObj.publicKey, aesKeyObj.key + aesKeyObj.iv);
85+
return aesCipherText;
86+
} catch (error) {
87+
console.debug('RSA公钥获取失败,错误详情:' + error);
88+
}
89+
}
90+
91+
async createTunnel() {
92+
if (!this.tunnelUrl) {
93+
try {
94+
const data = await this.getRSAPublicKey();
95+
if (!data) {
96+
throw 'fetch RSA publicKey failed';
97+
}
98+
// 创建隧道
99+
const response = await FetchRequest.post(`${this.serverUrl}services/security/tunnel/v1/tunnels`, data);
100+
const result = await response.json();
101+
Object.assign(this, {
102+
tunnelUrl: result.tunnelUrl,
103+
blockedUrlRegex: Object.assign({}, this.blockedUrlRegex, result.blockedUrlRegex)
104+
});
105+
} catch (error) {
106+
console.debug('安全隧道创建失败,错误详情:' + error);
107+
}
108+
}
109+
return this.tunnelUrl;
110+
}
111+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import pki from 'node-forge/lib/pki';
2+
import md from 'node-forge/lib/md';
3+
import cipher from 'node-forge/lib/cipher';
4+
import util from 'node-forge/lib/util';
5+
6+
/**
7+
* @private
8+
* @function RSAEncrypt
9+
* @description RSAES-OAEP/SHA-256/MGF1-SHA-1加密,对应java的RSA/ECB/OAEPWithSHA-256AndMGF1Padding
10+
* @param publicKeyStr - RSA 公钥
11+
* @param message - 需要加密的信息
12+
* @returns {string|boolean} 加密成功返回base64编码的密文,加密失败返回false
13+
*/
14+
export function RSAEncrypt (publicKeyStr, message) {
15+
if (publicKeyStr && publicKeyStr.indexOf('BEGIN PUBLIC KEY') === -1) { // 转为PEM格式
16+
publicKeyStr = `-----BEGIN PUBLIC KEY-----\n${publicKeyStr}\n-----END PUBLIC KEY-----`;
17+
}
18+
const publicKey = pki.publicKeyFromPem(publicKeyStr);
19+
const obj = {
20+
md: md.sha256.create(),
21+
mgf1: {
22+
md: md.sha1.create()
23+
}
24+
};
25+
const encrypted = publicKey.encrypt(message, 'RSA-OAEP', obj);
26+
if (!encrypted) {
27+
return false; // 加密失败
28+
}
29+
return window.btoa(encrypted);
30+
}
31+
32+
/**
33+
* @private
34+
* @function AESGCMDecrypt
35+
* @description AES/GCM解密
36+
* @param key - 16位
37+
* @param iv - 12位
38+
* @param cipherText - 密文 = base64转码(加密内容 + 16位的mac值)
39+
* @returns {boolean|string} 解密成功返回明文,解密失败返回false
40+
*/
41+
export function AESGCMDecrypt (key, iv, cipherText) {
42+
const cipherStrAndMac = window.atob(cipherText);
43+
const cipherStr = cipherStrAndMac.substring(0, cipherStrAndMac.length - 16);
44+
const mac = cipherStrAndMac.substring(cipherStrAndMac.length - 16);
45+
const decipher = cipher.createDecipher('AES-GCM', util.createBuffer(key));
46+
decipher.start({
47+
iv: util.createBuffer(iv),
48+
additionalData: '', // optional
49+
tagLength: 128, // optional, defaults to 128 bits
50+
tag: mac // authentication tag from encryption
51+
});
52+
decipher.update(util.createBuffer(cipherStr));
53+
const pass = decipher.finish();
54+
if (pass) {
55+
return util.decodeUtf8(decipher.output.data);
56+
}
57+
return false;
58+
}
59+
60+
/**
61+
* @private
62+
* @function AESGCMEncrypt
63+
* @description AES/GCM加密
64+
* @param key - 16位
65+
* @param iv - 12位
66+
* @param msg - 明文
67+
* @returns {boolean|string} 加密成功返回明文,加密失败返回false
68+
*/
69+
export function AESGCMEncrypt (key, iv, msg) {
70+
msg = util.encodeUtf8(msg);
71+
const cipherInstance = cipher.createCipher('AES-GCM', key);
72+
cipherInstance.start({
73+
iv: iv,
74+
additionalData: '', // 'binary-encoded string', // optional
75+
tagLength: 128 // optional, defaults to 128 bits
76+
});
77+
cipherInstance.update(util.createBuffer(msg));
78+
const pass = cipherInstance.finish();
79+
if (pass) {
80+
const encrypted = cipherInstance.output;
81+
const tag = cipherInstance.mode.tag;
82+
return window.btoa(encrypted.data + tag.data);
83+
}
84+
return false;
85+
}
86+
87+
/**
88+
* @private
89+
* @function generateAESRandomKey
90+
* @description 生成随机的16位 AES key
91+
* @returns {string}
92+
*/
93+
export function generateAESRandomKey () {
94+
return randomString(16);
95+
}
96+
97+
/**
98+
* @private
99+
* @function generateAESRandomIV
100+
* @description 生成随机的12位 AES iv
101+
* @returns {string}
102+
*/
103+
export function generateAESRandomIV () {
104+
return randomString(12);
105+
}
106+
107+
/**
108+
* @private
109+
* @function randomString
110+
* @description 生成指定长度的随机字符串
111+
* @param length - 随机字符串长度
112+
* @returns {string}
113+
*/
114+
function randomString (length) {
115+
const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
116+
let result = '';
117+
for (let i = length; i > 0; --i) { result += str[Math.floor(Math.random() * str.length)]; }
118+
return result;
119+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy