Skip to content

Commit f56b3df

Browse files
Merge branch 'tunnel-dev'
2 parents 7086f03 + dfa50ef commit f56b3df

File tree

18 files changed

+1045
-292
lines changed

18 files changed

+1045
-292
lines changed

dist/mapboxgl/include-mapboxgl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
inputScript(libsurl + '/mapbox-gl-js/1.13.2/mapbox-gl.js');
6060
}
6161
if (inArray(includes, 'mapbox-gl-enhance')) {
62-
inputCSS(libsurl + '/mapbox-gl-js-enhance/1.12.1-3/mapbox-gl-enhance.css');
63-
inputScript(libsurl + '/mapbox-gl-js-enhance/1.12.1-3/mapbox-gl-enhance.js');
62+
inputCSS(libsurl + '/mapbox-gl-js-enhance/1.12.1-4/mapbox-gl-enhance.css');
63+
inputScript(libsurl + '/mapbox-gl-js-enhance/1.12.1-4/mapbox-gl-enhance.js');
6464
}
6565
if (inArray(includes, 'g6')) {
6666
inputScript(libsurl + '/antv/g6/4.3.2/g6.min.js');

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
"@mapbox/vector-tile": "1.3.1",
134134
"@sinonjs/text-encoding": "^0.7.2",
135135
"@supermap/iclient-common": "file:src/common",
136+
"@supermap/tile-decryptor": "^0.0.1",
136137
"@tensorflow/tfjs": "^3.9.0",
137138
"@turf/turf": "6.5.0",
138139
"canvg": "3.0.10",
@@ -153,6 +154,7 @@
153154
"mapbox-gl": "1.13.2",
154155
"maplibre-gl": "3.1.0",
155156
"mapv": "2.0.62",
157+
"node-forge": "^1.3.1",
156158
"ol": "6.14.1",
157159
"pbf": "3.2.1",
158160
"process": "^0.11.10",

src/common/index.common.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ import {
333333
ArrayStatistic,
334334
getMeterPerMapUnit,
335335
getWrapNum,
336-
conversionDegree
336+
conversionDegree,
337+
EncryptRequest,
338+
getServiceKey
337339
} from './util';
338340
import { CartoCSS, ThemeStyle } from './style';
339341
import {
@@ -498,6 +500,8 @@ export {
498500
isCORS,
499501
setCORS,
500502
FetchRequest,
503+
EncryptRequest,
504+
getServiceKey,
501505
ColorsPickerUtil,
502506
ArrayStatistic,
503507
getMeterPerMapUnit,

src/common/namespace.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ import {
337337
isCORS,
338338
setCORS,
339339
FetchRequest,
340+
EncryptRequest,
341+
getServiceKey,
340342
ColorsPickerUtil,
341343
ArrayStatistic,
342344
CartoCSS,
@@ -493,7 +495,8 @@ SuperMap.isCORS = isCORS;
493495
SuperMap.setRequestTimeout = setRequestTimeout;
494496
SuperMap.getRequestTimeout = getRequestTimeout;
495497
SuperMap.FetchRequest = FetchRequest;
496-
498+
SuperMap.EncryptRequest = EncryptRequest;
499+
SuperMap.getServiceKey = getServiceKey;
497500
// commontypes
498501
SuperMap.inherit = inheritExt;
499502
SuperMap.mixin = mixinExt;

src/common/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515
"license": "Apache-2.0",
1616
"dependencies": {
1717
"@antv/g6": "^4.8.14",
18+
"@supermap/tile-decryptor": "^0.0.1",
1819
"echarts": "5.4.3",
1920
"fetch-ie8": "1.5.0",
2021
"fetch-jsonp": "1.1.3",
22+
"flatgeobuf": "3.23.1",
2123
"promise-polyfill": "8.2.3",
2224
"lodash.topairs": "4.3.0",
2325
"lodash.uniqby": "^4.7.0",
2426
"lodash.clonedeep": "^4.5.0",
2527
"mapv": "2.0.62",
26-
"flatgeobuf": "3.23.1",
28+
"node-forge": "^1.3.1",
2729
"rbush": "^2.0.2",
2830
"urijs": "^1.19.11",
2931
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"

src/common/util/EncryptRequest.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { FetchRequest } from './FetchRequest';
2+
import {
3+
RSAEncrypt,
4+
AESGCMEncrypt,
5+
AESGCMDecrypt,
6+
generateAESRandomKey,
7+
generateAESRandomIV
8+
} from './RequestcryptUtil';
9+
import URI from 'urijs';
10+
11+
/**
12+
* @name EncryptRequest
13+
* @version 11.2.0
14+
* @namespace
15+
* @category BaseTypes Util
16+
* @classdesc 加密请求地址
17+
* @param {string} serverUrl - 服务地址。
18+
*/
19+
export class EncryptRequest {
20+
constructor(serverUrl) {
21+
this.serverUrl = serverUrl;
22+
this.tunnelUrl = undefined;
23+
this.blockedUrlRegex = {
24+
HEAD: [],
25+
POST: [],
26+
GET: [],
27+
PUT: [],
28+
DELETE: []
29+
};
30+
this.encryptAESKey = generateAESRandomKey();
31+
this.encryptAESIV = generateAESRandomIV();
32+
}
33+
34+
/**
35+
* @function EncryptRequest.prototype.request
36+
* @description 加密请求地址。
37+
* @param {Object} config - 加密请求参数。
38+
* @param {string} config.method - 请求方法。
39+
* @param {string} config.url - 请求地址。
40+
* @param {string} config.params - 请求参数。
41+
* @param {Object} config.options - 请求的配置属性。
42+
* @returns {Promise} Promise 对象。
43+
*/
44+
async request(options) {
45+
if (!this.serverUrl) {
46+
throw 'serverUrl can not be empty.';
47+
}
48+
const config = Object.assign({ baseURL: '' }, options);
49+
const tunnelUrl = await this._createTunnel();
50+
if (!tunnelUrl) {
51+
return;
52+
}
53+
for (const pattern of this.blockedUrlRegex[config.method.toUpperCase()]) {
54+
const regex = new RegExp(pattern);
55+
if (regex.test(config.baseURL + config.url)) {
56+
const data = {
57+
url: config.baseURL + (config.url.startsWith('/') ? config.url.substring(1, config.url.length) : config.url),
58+
method: config.method,
59+
timeout: config.timeout,
60+
headers: config.headers,
61+
body: config.data
62+
};
63+
// 替换请求
64+
config.method = 'post';
65+
config.data = AESGCMEncrypt(this.encryptAESKey, this.encryptAESIV, JSON.stringify(data));
66+
if (!config.data) {
67+
throw 'encrypt failed';
68+
}
69+
config.url = this.tunnelUrl;
70+
break;
71+
}
72+
}
73+
const response = await FetchRequest.commit(config.method, config.url, config.data, config.options);
74+
if (config.url === this.tunnelUrl) {
75+
const result = await response.text();
76+
const decryptResult = AESGCMDecrypt(this.encryptAESKey, this.encryptAESIV, result);
77+
if (!decryptResult) {
78+
console.debug('解密请求响应失败');
79+
return;
80+
}
81+
const resultData = JSON.parse(decryptResult);
82+
const resData = Object.create({
83+
json: function () {
84+
return Promise.resolve(resultData.data);
85+
}
86+
});
87+
return Object.assign(resData, resultData);
88+
}
89+
return response;
90+
}
91+
92+
/**
93+
* @private
94+
* @description 获取RSA public key
95+
* @function EncryptRequest.prototype._getRSAPublicKey
96+
*/
97+
async _getRSAPublicKey() {
98+
try {
99+
const response = await FetchRequest.get(URI(this.serverUrl).segment('services/security/tunnel/v1/publickey').toString());
100+
// 解析publicKey
101+
const publicKeyObj = await response.json();
102+
// 生成AES密钥
103+
const aesKeyObj = {
104+
key: this.encryptAESKey,
105+
iv: this.encryptAESIV,
106+
mode: 'GCM',
107+
padding: 'NoPadding'
108+
};
109+
// 将AES密钥使用RSA公钥加密
110+
const aesCipherText = RSAEncrypt(publicKeyObj.publicKey, aesKeyObj.key + aesKeyObj.iv);
111+
return aesCipherText;
112+
} catch (error) {
113+
console.debug('RSA公钥获取失败,错误详情:' + error);
114+
}
115+
}
116+
117+
/**
118+
* @private
119+
* @description 创建隧道
120+
* @function EncryptRequest.prototype._createTunnel
121+
*/
122+
async _createTunnel() {
123+
if (!this.tunnelUrl) {
124+
try {
125+
const data = await this._getRSAPublicKey();
126+
if (!data) {
127+
throw 'fetch RSA publicKey failed';
128+
}
129+
// 创建隧道
130+
const response = await FetchRequest.post(URI(this.serverUrl).segment('services/security/tunnel/v1/tunnels').toString(), data);
131+
const result = await response.json();
132+
Object.assign(this, {
133+
tunnelUrl: result.tunnelUrl,
134+
blockedUrlRegex: Object.assign({}, this.blockedUrlRegex, result.blockedUrlRegex)
135+
});
136+
} catch (error) {
137+
console.debug('安全隧道创建失败,错误详情:' + error);
138+
}
139+
}
140+
return this.tunnelUrl;
141+
}
142+
}
143+
144+
/**
145+
* @function getServiceKey
146+
* @version 11.2.0
147+
* @category iServer
148+
* @description 获取矢量瓦片解密密钥
149+
* @param {string} serviceUrl - iserver服务地址,例如: 'http://127.0.0.1:8090/iserver/services/xxx'、 'http://127.0.0.1:8090/iserver/services/xxx/rest/maps' 、 'http://127.0.0.1:8090/iserver/services/xxx/restjsr/v1/vectortile'
150+
* @return {Promise} key - 矢量瓦片密钥
151+
*/
152+
export async function getServiceKey(serviceUrl) {
153+
try {
154+
const workspaceServerUrl = ((serviceUrl &&
155+
serviceUrl.match(/.+(?=(\/restjsr\/v1\/vectortile\/|\/rest\/maps\/))/)) ||
156+
[])[0];
157+
if (!workspaceServerUrl) {
158+
return;
159+
}
160+
const servicesResponse = await FetchRequest.get(workspaceServerUrl);
161+
const servicesResult = await servicesResponse.json();
162+
const matchRestData = (servicesResult || []).find(
163+
(item) => serviceUrl.includes(item.name) && item.serviceEncryptInfo
164+
);
165+
if (!matchRestData) {
166+
return;
167+
}
168+
const iserverHost = workspaceServerUrl.split('/services/')[0];
169+
const encryptRequest = new EncryptRequest(iserverHost);
170+
const svckeyUrl =
171+
matchRestData && `${iserverHost}/services/security/svckeys/${matchRestData.serviceEncryptInfo.encrptKeyID}.json`;
172+
const svcReponse = await encryptRequest.request({
173+
method: 'get',
174+
url: svckeyUrl
175+
});
176+
return svcReponse.json();
177+
} catch (error) {
178+
console.error(error);
179+
}
180+
}

src/common/util/RequestcryptUtil.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
import { pki, md, cipher, util } from 'node-forge/dist/forge.min';
6+
7+
/**
8+
* @private
9+
* @function RSAEncrypt
10+
* @description RSAES-OAEP/SHA-256/MGF1-SHA-1加密,对应java的RSA/ECB/OAEPWithSHA-256AndMGF1Padding
11+
* @param publicKeyStr - RSA 公钥
12+
* @param message - 需要加密的信息
13+
* @returns {string|boolean} 加密成功返回base64编码的密文,加密失败返回false
14+
*/
15+
export function RSAEncrypt (publicKeyStr, message) {
16+
if (publicKeyStr && publicKeyStr.indexOf('BEGIN PUBLIC KEY') === -1) { // 转为PEM格式
17+
publicKeyStr = `-----BEGIN PUBLIC KEY-----\n${publicKeyStr}\n-----END PUBLIC KEY-----`;
18+
}
19+
const publicKey = pki.publicKeyFromPem(publicKeyStr);
20+
const obj = {
21+
md: md.sha256.create(),
22+
mgf1: {
23+
md: md.sha1.create()
24+
}
25+
};
26+
const encrypted = publicKey.encrypt(message, 'RSA-OAEP', obj);
27+
if (!encrypted) {
28+
return false; // 加密失败
29+
}
30+
return window.btoa(encrypted);
31+
}
32+
33+
/**
34+
* @private
35+
* @function AESGCMDecrypt
36+
* @description AES/GCM解密
37+
* @param key - 16位
38+
* @param iv - 12位
39+
* @param cipherText - 密文 = base64转码(加密内容 + 16位的mac值)
40+
* @returns {boolean|string} 解密成功返回明文,解密失败返回false
41+
*/
42+
export function AESGCMDecrypt (key, iv, cipherText) {
43+
const cipherStrAndMac = window.atob(cipherText);
44+
const cipherStr = cipherStrAndMac.substring(0, cipherStrAndMac.length - 16);
45+
const mac = cipherStrAndMac.substring(cipherStrAndMac.length - 16);
46+
const decipher = cipher.createDecipher('AES-GCM', util.createBuffer(key));
47+
decipher.start({
48+
iv: util.createBuffer(iv),
49+
additionalData: '', // optional
50+
tagLength: 128, // optional, defaults to 128 bits
51+
tag: mac // authentication tag from encryption
52+
});
53+
decipher.update(util.createBuffer(cipherStr));
54+
const pass = decipher.finish();
55+
if (pass) {
56+
return util.decodeUtf8(decipher.output.data);
57+
}
58+
return false;
59+
}
60+
61+
/**
62+
* @private
63+
* @function AESGCMEncrypt
64+
* @description AES/GCM加密
65+
* @param key - 16位
66+
* @param iv - 12位
67+
* @param msg - 明文
68+
* @returns {boolean|string} 加密成功返回明文,加密失败返回false
69+
*/
70+
export function AESGCMEncrypt (key, iv, msg) {
71+
msg = util.encodeUtf8(msg);
72+
const cipherInstance = cipher.createCipher('AES-GCM', key);
73+
cipherInstance.start({
74+
iv: iv,
75+
additionalData: '', // 'binary-encoded string', // optional
76+
tagLength: 128 // optional, defaults to 128 bits
77+
});
78+
cipherInstance.update(util.createBuffer(msg));
79+
const pass = cipherInstance.finish();
80+
if (pass) {
81+
const encrypted = cipherInstance.output;
82+
const tag = cipherInstance.mode.tag;
83+
return window.btoa(encrypted.data + tag.data);
84+
}
85+
return false;
86+
}
87+
88+
/**
89+
* @private
90+
* @function generateAESRandomKey
91+
* @description 生成随机的16位 AES key
92+
* @returns {string}
93+
*/
94+
export function generateAESRandomKey () {
95+
return randomString(16);
96+
}
97+
98+
/**
99+
* @private
100+
* @function generateAESRandomIV
101+
* @description 生成随机的12位 AES iv
102+
* @returns {string}
103+
*/
104+
export function generateAESRandomIV () {
105+
return randomString(12);
106+
}
107+
108+
/**
109+
* @private
110+
* @function randomString
111+
* @description 生成指定长度的随机字符串
112+
* @param length - 随机字符串长度
113+
* @returns {string}
114+
*/
115+
function randomString (length) {
116+
const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
117+
let result = '';
118+
for (let i = length; i > 0; --i) { result += str[Math.floor(Math.random() * str.length)]; }
119+
return result;
120+
}

0 commit comments

Comments
 (0)