Pārlūkot izejas kodu

数据加密处理

lrf 10 mēneši atpakaļ
vecāks
revīzija
a74f69a97e

+ 10 - 0
certs/private.pem

@@ -0,0 +1,10 @@
+-----BEGIN PRIVATE KEY-----
+MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAo+q6+0XzFbaCSOg5
+vh7ggWvJ4vUa5b4/JmLgCco1TRadh02Y0N4jviZWHzGP6Ujs5bnASyB92pdtZPnW
+65aB/QIDAQABAkB431PojMlXHpFuP8isuMomxZxG/yVJewPIQ6sfrPunDq9lM56q
+ITUdN5iXV8GmBiVTOujp6pYT5AgMLADKrbeFAiEA74CmxaOeYDsHdyv1Q6zFY/Sr
+jQqHDFiiYBwxVxlMuTcCIQCvNTbhAP8ewUeukL9PMCgLWj83APNI4sZAROvWYU+o
+awIgXt2/sVNg/U8EYwDncnnx6ejVNtWvroVBM/6W0KA10rsCIGt+Kn1BL0SfMGtZ
+QwZCdU8Bv2bvnlNJTnh/0HFmooihAiEA73CnnEqfFkY8CeMUJIprU/3KBtgepw5p
+3FlKic20B3U=
+-----END PRIVATE KEY-----

+ 4 - 0
certs/public.pem

@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPquvtF8xW2gkjoOb4e4IFryeL1GuW+
+PyZi4AnKNU0WnYdNmNDeI74mVh8xj+lI7OW5wEsgfdqXbWT51uuWgf0CAwEAAQ==
+-----END PUBLIC KEY-----

+ 51 - 1
package-lock.json

@@ -20,8 +20,10 @@
         "@midwayjs/redis": "^3.16.0",
         "@midwayjs/validate": "^3.12.0",
         "@types/crypto-js": "^4.2.2",
+        "@types/node-rsa": "^1.1.4",
         "crypto-js": "^4.2.0",
-        "lodash": "^4.17.21"
+        "lodash": "^4.17.21",
+        "node-rsa": "^1.1.1"
       },
       "devDependencies": {
         "@midwayjs/mock": "^3.12.0",
@@ -1641,6 +1643,14 @@
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz",
       "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
     },
+    "node_modules/@types/node-rsa": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@types/node-rsa/-/node-rsa-1.1.4.tgz",
+      "integrity": "sha512-dB0ECel6JpMnq5ULvpUTunx3yNm8e/dIkv8Zu9p2c8me70xIRUUG3q+qXRwcSf9rN3oqamv4116iHy90dJGRpA==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/normalize-package-data": {
       "version": "2.4.4",
       "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
@@ -2053,6 +2063,14 @@
       "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
       "dev": true
     },
+    "node_modules/asn1": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz",
+      "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+      "dependencies": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
     "node_modules/astral-regex": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -5864,6 +5882,14 @@
       "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
       "dev": true
     },
+    "node_modules/node-rsa": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/node-rsa/-/node-rsa-1.1.1.tgz",
+      "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==",
+      "dependencies": {
+        "asn1": "^0.2.4"
+      }
+    },
     "node_modules/normalize-package-data": {
       "version": "3.0.3",
       "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
@@ -8959,6 +8985,14 @@
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz",
       "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
     },
+    "@types/node-rsa": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@types/node-rsa/-/node-rsa-1.1.4.tgz",
+      "integrity": "sha512-dB0ECel6JpMnq5ULvpUTunx3yNm8e/dIkv8Zu9p2c8me70xIRUUG3q+qXRwcSf9rN3oqamv4116iHy90dJGRpA==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/normalize-package-data": {
       "version": "2.4.4",
       "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
@@ -9270,6 +9304,14 @@
       "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
       "dev": true
     },
+    "asn1": {
+      "version": "0.2.6",
+      "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz",
+      "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
     "astral-regex": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -12273,6 +12315,14 @@
       "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
       "dev": true
     },
+    "node-rsa": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/node-rsa/-/node-rsa-1.1.1.tgz",
+      "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==",
+      "requires": {
+        "asn1": "^0.2.4"
+      }
+    },
     "normalize-package-data": {
       "version": "3.0.3",
       "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz",

+ 3 - 1
package.json

@@ -15,8 +15,10 @@
     "@midwayjs/redis": "^3.16.0",
     "@midwayjs/validate": "^3.12.0",
     "@types/crypto-js": "^4.2.2",
+    "@types/node-rsa": "^1.1.4",
     "crypto-js": "^4.2.0",
-    "lodash": "^4.17.21"
+    "lodash": "^4.17.21",
+    "node-rsa": "^1.1.1"
   },
   "devDependencies": {
     "@midwayjs/mock": "^3.12.0",

+ 2 - 0
src/config/config.default.ts

@@ -6,6 +6,8 @@ export default {
   koa: {
     port: 19700,
   },
+  //是否启用数据加密,需要与前端配合一致,否则会一端加密,一端不解密
+  useCrypto: true,
   jwt: {
     secret: 'Ziyouyanfa!@#',
     expiresIn: 3600, // 3600

+ 9 - 2
src/controller/home.controller.ts

@@ -3,6 +3,7 @@ import { Context } from '@midwayjs/koa';
 import { ProxyService } from '../service/proxy.service';
 import { SingleSignOnService } from '../service/singleSignOn.service';
 import { PermissionService } from '../service/permission.service';
+import { PemService } from '../service/pem.service';
 @Controller('/')
 export class HomeController {
   @Inject()
@@ -17,13 +18,19 @@ export class HomeController {
   @Inject()
   permissionService: PermissionService;
 
+  @Inject()
+  pemService: PemService;
+
   @Get('/')
   async home(): Promise<string> {
     return 'proxy starting....';
   }
 
-  @Put('/init')
-  async makePairCrypto() {}
+  @Get('/initKeys')
+  async makePairCrypto() {
+    await this.pemService.createKeys();
+    return 'makePairCrypto';
+  }
 
   @All('/**')
   async proxy() {

+ 1 - 1
src/interface/proxy.interface.ts

@@ -5,7 +5,7 @@ export interface RequestBase {
   path: string;
   query?: object;
   querystring?: string;
-  body?: object;
+  body?: object | string;
   rawBody?: string;
   host: string;
   ip?: string;

+ 101 - 0
src/service/pem.service.ts

@@ -0,0 +1,101 @@
+import { App, Config, Inject, Provide } from '@midwayjs/core';
+import { Application } from '@midwayjs/koa';
+import * as crypto from 'crypto';
+import * as path from 'path';
+import * as fs from 'fs';
+import { ProxyService } from './proxy.service';
+import { RequestBase } from '../interface/proxy.interface';
+import * as CryptoJS from 'crypto-js';
+import { get, isObject } from 'lodash';
+import * as NodeRSA from 'node-rsa';
+@Provide()
+export class PemService {
+  // pemDir = `${process.pwd()}`;
+  @App()
+  app: Application;
+  /**证书目录 */
+  certsDir = 'certs';
+  pemPublicName = 'public.pem';
+  pemPrivateName = 'private.pem';
+
+  @Config('jwt.secret')
+  jwtSecret: string;
+  @Inject()
+  proxyService: ProxyService;
+
+  /**
+   * 根据请求头,获取真实使用加密数据的字符串
+   * @returns {string} 真实使用加密数据的字符串
+   */
+  decryptRequestCode() {
+    const rb: RequestBase = this.proxyService.getRequstBase();
+    // 获取加密字符串
+    const enReqCode = get(rb, 'header.api-token');
+    // 使用RSA解密 加密字符串, 获得真实使用的加密字符串
+    const appPath = this.app.getAppDir();
+    const certsDirPath = path.resolve(appPath, this.certsDir);
+    const privatePath = path.resolve(certsDirPath, this.pemPrivateName);
+    const privateKey = fs.readFileSync(privatePath, { encoding: 'utf8' });
+    // 解密
+    const decrypt = new NodeRSA(privateKey, 'pkcs8-private-pem');
+    // 与前端加密保持一致
+    decrypt.setOptions({ encryptionScheme: 'pkcs1' });
+    const res = decrypt.decrypt(enReqCode, 'utf8');
+    return res;
+  }
+  /**
+   * 加密: 用于请求返回给来源时
+   * @param data 需要加密的数据
+   */
+  encrypt(data: object | string) {
+    const code = this.decryptRequestCode();
+    if (!code) throw new Error('加密字符串解析错误');
+    let strData = data;
+    if (isObject(strData)) strData = JSON.stringify(data);
+    const ivStr = code.substring(0, 16);
+    const key = CryptoJS.enc.Utf8.parse(code);
+    const iv = CryptoJS.enc.Utf8.parse(ivStr);
+    const srcs = CryptoJS.enc.Utf8.parse(strData);
+    const encrypted = CryptoJS.AES.encrypt(srcs, key, {
+      iv,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.Pkcs7,
+    });
+    return encrypted.toString();
+  }
+  /**
+   * 解密:用于请求代理前
+   * @param data body数据
+   */
+  decrypt(data: string) {
+    const code = this.decryptRequestCode();
+    if (!code) throw new Error('加密字符串解析错误');
+    const ivStr = code.substring(0, 16);
+    const key = CryptoJS.enc.Utf8.parse(code);
+    const iv = CryptoJS.enc.Utf8.parse(ivStr);
+    const decrypt = CryptoJS.AES.decrypt(data, key, {
+      iv,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.Pkcs7,
+    });
+    return decrypt.toString(CryptoJS.enc.Utf8);
+  }
+
+  /**生成证书 */
+  createKeys() {
+    const key = new NodeRSA({ b: 512 });
+    key.setOptions({ encryptionScheme: 'pkcs1' });
+    //制定输出格式
+    const publicKey = key.exportKey('pkcs8-public-pem');
+    const privateKey = key.exportKey('pkcs8-private-pem');
+    // 获取项目路径,生成certs文件夹
+    const appPath = this.app.getAppDir();
+    const certsDirPath = path.resolve(appPath, this.certsDir);
+    const is_exists = fs.existsSync(certsDirPath);
+    if (!is_exists) fs.mkdirSync(certsDirPath);
+    const publicPath = path.resolve(certsDirPath, this.pemPublicName);
+    fs.writeFileSync(publicPath, publicKey, { encoding: 'utf8' });
+    const privatePath = path.resolve(certsDirPath, this.pemPrivateName);
+    fs.writeFileSync(privatePath, privateKey, { encoding: 'utf8' });
+  }
+}

+ 35 - 7
src/service/proxy.service.ts

@@ -1,22 +1,30 @@
-import { Config, Inject, InjectClient, Provide } from '@midwayjs/core';
-import { Context } from '@midwayjs/koa';
+import { App, Config, Inject, InjectClient, Provide } from '@midwayjs/core';
+import { Application, Context } from '@midwayjs/koa';
 import { RequestBase } from '../interface/proxy.interface';
-import { get, lowerCase, pick } from 'lodash';
+import { get, isObject, isString, lowerCase, omit, pick } from 'lodash';
 import { HttpServiceFactory, HttpService } from '@midwayjs/axios';
-import { Axios } from '@midwayjs/axios';
+import { PemService } from './pem.service';
 @Provide()
 export class ProxyService {
   @Inject()
   ctx: Context;
+  @App()
+  app: Application;
 
   @Config('axios.clients')
   axiosClients: object;
 
+  @Config('useCrypto')
+  useCrypto: boolean;
+
   @InjectClient(HttpServiceFactory, 'default')
   serviceAxios: HttpService;
 
   @Inject()
   sf: HttpServiceFactory;
+
+  @Inject()
+  pemService: PemService;
   /**
    * 组织请求所需要的参数
    * @returns {RequestBase} result
@@ -62,15 +70,35 @@ export class ProxyService {
       break;
     }
     if (!url) return;
-    console.log(url);
     const reqConfig: any = {
       url,
       method: rb.method,
       headers: rb.header,
     };
-    if (lowerCase(rb.method) === 'post') reqConfig.data = rb.body;
+    if (lowerCase(rb.method) === 'post') {
+      // 如果是post函数,且 不属于开发模式,则解密函数体.否则不用解密
+      if (this.useCrypto) {
+        let preBody;
+        if (isString(rb.body)) preBody = rb.body;
+        else if (isObject(rb.body)) preBody = JSON.stringify(rb.body);
+        const deBody = this.pemService.decrypt(preBody);
+        reqConfig.data = deBody;
+      } else {
+        reqConfig.data = rb.body;
+      }
+    }
     const res = await this.serviceAxios.request(reqConfig);
     if (res.status !== 200) throw new Error('proxy service request error');
-    return res.data;
+    const result = res.data;
+    if (this.useCrypto) {
+      // 需要将返回的数据加密成字符串
+      if (get(result, 'data')) {
+        const enReturn = this.pemService.encrypt(
+          omit(res.data, ['errcode', 'errmsg'])
+        );
+        result.data = enReturn;
+      }
+    }
+    return result;
   }
 }