|
|
@@ -1,38 +1,134 @@
|
|
|
-const express = require('express');
|
|
|
-const Logger = require('./Logger');
|
|
|
-const path = require('path');
|
|
|
+const express = require('express')
|
|
|
+const Logger = require('./Logger')
|
|
|
+const path = require('path')
|
|
|
+const fs = require('fs')
|
|
|
+const forge = require('node-forge')
|
|
|
+const crypto = require('crypto')
|
|
|
+const { BaseStdResponse } = require("../BaseStdResponse")
|
|
|
|
|
|
class API {
|
|
|
constructor() {
|
|
|
- this.router = express.Router();
|
|
|
- this.namespace = '';
|
|
|
- this.path = '';
|
|
|
- this.method = 'get';
|
|
|
+ this.router = express.Router()
|
|
|
+ this.namespace = ''
|
|
|
+ this.path = ''
|
|
|
+ this.method = 'get'
|
|
|
+ this.encrypt = true
|
|
|
|
|
|
- this.logger = new Logger(path.join(__dirname, '../logs/API.log'), 'INFO');
|
|
|
+ this.privateKey = fs.readFileSync(path.join(__dirname, '../keys/private_key.pem'), 'utf8')
|
|
|
+
|
|
|
+ this.logger = new Logger(path.join(__dirname, '../logs/API.log'), 'INFO')
|
|
|
+ }
|
|
|
+
|
|
|
+ noEncrypt() {
|
|
|
+ this.encrypt = false
|
|
|
}
|
|
|
|
|
|
setPath(path) {
|
|
|
- this.path = path;
|
|
|
+ this.path = path
|
|
|
}
|
|
|
|
|
|
setMethod(method) {
|
|
|
- this.method = method.toLowerCase();
|
|
|
+ this.method = method.toLowerCase()
|
|
|
}
|
|
|
|
|
|
getRouter() {
|
|
|
- return this.router;
|
|
|
+ return this.router
|
|
|
}
|
|
|
|
|
|
async onRequest(req, res) {
|
|
|
- throw new Error('onRequest方法未实现');
|
|
|
+ throw new Error('onRequest方法未实现')
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解密数据:使用 RSA 解密 AES 密钥,再用 AES 解密数据
|
|
|
+ */
|
|
|
+ decryptPayload(req, res, next) {
|
|
|
+ if(!this.encrypt)
|
|
|
+ return next()
|
|
|
+ try {
|
|
|
+ let encryptedKey, encryptedData
|
|
|
+ if (this.method === 'get') {
|
|
|
+ encryptedKey = req.query.encryptedKey
|
|
|
+ encryptedData = req.query.encryptedData
|
|
|
+ } else {
|
|
|
+ encryptedKey = req.body.encryptedKey
|
|
|
+ encryptedData = req.body.encryptedData
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!encryptedKey || !encryptedData)
|
|
|
+ return res.json({
|
|
|
+ ...BaseStdResponse.MISSING_PARAMETER,
|
|
|
+ msg: '参数错误。请刷新页面或更新客户端版本'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 1. 解密 AES 密钥(RSA)
|
|
|
+ const privateKey = forge.pki.privateKeyFromPem(this.privateKey)
|
|
|
+ const aesKey = privateKey.decrypt(forge.util.decode64(encryptedKey), 'RSAES-PKCS1-V1_5')
|
|
|
+
|
|
|
+ // 2. 解密数据(AES)
|
|
|
+ const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(aesKey, 'utf8'), null)
|
|
|
+ decipher.setAutoPadding(true)
|
|
|
+ let decrypted = decipher.update(encryptedData, 'base64', 'utf8')
|
|
|
+ decrypted += decipher.final('utf8')
|
|
|
+ decrypted = JSON.parse(decrypted)
|
|
|
+
|
|
|
+ const time = Date.now()
|
|
|
+ if (Math.abs(time - decrypted.time) > 10 * 60 * 1000)
|
|
|
+ return res.json({
|
|
|
+ ...BaseStdResponse.ERR,
|
|
|
+ msg: '请检查计算机时间是否正确!'
|
|
|
+ })
|
|
|
+
|
|
|
+ decrypted.aesKey = aesKey
|
|
|
+
|
|
|
+ if (this.method === 'get')
|
|
|
+ req.query = decrypted
|
|
|
+ else
|
|
|
+ req.body = decrypted
|
|
|
+
|
|
|
+ const requestId = req.headers['x-request-id'] || ''
|
|
|
+ res.setHeader('X-Request-ID', requestId)
|
|
|
+
|
|
|
+ next()
|
|
|
+ } catch (err) {
|
|
|
+ console.error('解密失败:', err)
|
|
|
+ return res.json({
|
|
|
+ ...BaseStdResponse.ERR,
|
|
|
+ msg: '参数错误'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ aesEncrypt(data, req) {
|
|
|
+ let aesKey
|
|
|
+ if (this.method === 'get')
|
|
|
+ aesKey = req.query.aesKey
|
|
|
+ else
|
|
|
+ aesKey = req.body.aesKey
|
|
|
+
|
|
|
+ const cipher = crypto.createCipheriv('aes-128-ecb', Buffer.from(aesKey, 'utf8'), null)
|
|
|
+ cipher.setAutoPadding(true)
|
|
|
+ let encryptedData = cipher.update(JSON.stringify(data), 'utf8', 'base64')
|
|
|
+ encryptedData += cipher.final('base64')
|
|
|
+ return encryptedData
|
|
|
}
|
|
|
|
|
|
setupRoute() {
|
|
|
- this.router[this.method](this.path, async (req, res) => {
|
|
|
- await this.onRequest(req, res);
|
|
|
- });
|
|
|
+ this.router[this.method](this.path, this.decryptPayload.bind(this), async (req, res) => {
|
|
|
+ // 拦截返回值统一加密
|
|
|
+ const originalJson = res.json.bind(res)
|
|
|
+ res.json = (data) => {
|
|
|
+ if(this.encrypt && data.data) {
|
|
|
+ data.encryptedData = this.aesEncrypt(data.data, req)
|
|
|
+ delete data.data
|
|
|
+ }
|
|
|
+
|
|
|
+ return originalJson(data)
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.onRequest(req, res)
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-module.exports = API;
|
|
|
+module.exports = API
|