前言
在常見前端SAP項目中,瀏覽器端經過HTTP請求獲取數據,這個過程能夠經過打開瀏覽器控制檯看到結果,
平時開發過程當中也會有經過工具模擬HTTP請求的過程,例如postman等工具,能夠經過工具直接得到數據
公司新項目上線後,安全組的同事對系統進行掃描排查,提出了一堆敏感信息問題...
複製代碼
存在問題
解決思路
由於以前有爆出說客戶信息泄露的問題,爲了防微杜漸,系統仍是有必要對敏感信息進行加密
解決方案能夠按安全組同事的建議對提出的某些字段在代碼上進行單個加密後解密
可是這樣的話會存在一個問題,須要改動到不少地方的代碼,另外本身在排查的時候也會增長難度
後續若是增長鬚要繼續加密的字段的話又要改動代碼,後續維護的成本太高
若是能在同一的請求方法那裏對請求參數進行加密,在服務端統一進行解密,那就不用改動到代碼
後續安全組繼續提出敏感信息問題的話也能夠不用改動到代碼
複製代碼
項目架構
前端 react + axios
服務端 koa2
複製代碼
react + axios改造
引入加解密模塊crypto-browserify
npm i --save crypto-browserify
複製代碼
加解密方法封裝
const cryptoConfig = {
algorithm: 'aes-128-ecb',
key: 'abcd1234abcd1234', // 16 B 128 bits
}
export default {
encrypt: function (data) {
try {
if (!data) {
return data;
// throw '加密參數錯誤';
}
let jsonData = JSON.stringify(data);
if (Object.prototype.toString.call(jsonData) !== "[object String]") {
return data;
// throw '加密參數類型錯誤, 只能是 String 類型';
}
let cipher = crypto.createCipheriv(cryptoConfig.algorithm, cryptoConfig.key, '');
let encrypted = cipher.update(jsonData, 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log(encrypted);
return encrypted;
} catch (e) {
console.error(e);
return data;
}
},
decrypt: function (data) {
try {
if (!data) {
// throw '加密參數錯誤';
return data;
}
if (Object.prototype.toString.call(data) !== "[object String]") {
// throw '加密參數類型錯誤, 只能是 String 類型';
return data;
}
const decipher = crypto.createDecipheriv(cryptoConfig.algorithm, cryptoConfig.key, '');
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
let parseResp = decrypted;
if (decrypted) {
parseResp = JSON.parse(decrypted);
}
return parseResp;
} catch (e) {
console.error(e);
return data;
}
}
};
複製代碼
對axios請求方法進行改造,以post方法爲例
const fetch = (url, options) => {
const { method = 'get', data } = options
switch (method.toLowerCase()) {
...
case 'post':
return cryptoPost(url, data);
default:
return axios(options)
}
}
// 統一對請求參數進行加密,返回結果進行解密
const cryptoPost = (url, data) => {
return new Promise(async (resolve, reject) => {
let resp;
let encryptParam = Utils.encrypt(data);
resp = await axios.post(url, {encryptParam}, { headers:{'content-type': 'application/json'}, 'withCredentials': true})
if (resp && resp.data) {
resp.data = Utils.decrypt(resp.data);
}
resolve(resp);
});
}
複製代碼
koa2加解密改造
加解密方法封裝(基本同前端同樣)
const crypto = require('crypto');
const cryptoConfig = {
algorithm: 'aes-128-ecb',
key: 'abcd1234abcd1234', // 16 B 128 bits
}
exports.encrypt = (data) => {
try {
if (!data) {
// throw '加密參數錯誤';
return data;
}
let jsonData = JSON.stringify(data);
if (Object.prototype.toString.call(jsonData) !== "[object String]") {
// throw '加密參數類型錯誤, 只能是 String 類型';
return data;
}
let cipher = crypto.createCipheriv(config.cryptoConfig.algorithm, config.cryptoConfig.key, '');
let encrypted = cipher.update(jsonData, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
} catch (e) {
console.error(e);
return data;
}
};
exports.decrypt = (data) => {
try {
if (!data) {
// throw '加密參數錯誤';
return data;
}
if (Object.prototype.toString.call(data) !== "[object String]") {
// throw '加密參數類型錯誤, 只能是 String 類型';
return data;
}
const decipher = crypto.createDecipheriv(config.cryptoConfig.algorithm, config.cryptoConfig.key, '');
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
let parseResp = decrypted;
if (decrypted) {
parseResp = JSON.parse(decrypted);
}
return parseResp;
} catch (e) {
console.error(e);
return data;
}
};
複製代碼
在中間件中使用加解密
app.use(async (ctx, next) => {
// get請求和指定白名單的請求不走這個判斷
if (['/api/checkToken', '/api/autoLogin'].includes(ctx.request.originalUrl) || ctx.method === 'GET') {
await next();
} else {
let needDecryptAndEncrypt = false;
if (ctx.request.body) {
if (ctx.request.body.encryptParam) {
needDecryptAndEncrypt = true;
let body = decrypt(ctx.request.body.encryptParam);
ctx.request.body = body;
}
}
await next();
if (ctx.body && needDecryptAndEncrypt) {
let respBody = encrypt(ctx.body);
ctx.body = respBody;
}
}
});
複製代碼
改造結果
請求參數
返回參數
系統使用正常,與以前無異
調試問題
請求參數和返回參數都加密了,這個對前端排查問題來講是一件很痛苦的事,因此最好根據須要
增長一個後門方便前端以明文的方式查看參數
思路是在前端axios方法那邊作一些特殊處理......
複製代碼
加了後門之後的方法
const cryptoPost = (url, data) => {
return new Promise(async (resolve, reject) => {
let resp;
if (localStorage.getItem('plaintextMode') == 1) { // 明文模式不作處理
resp = await axios.post(url, data, { headers:{'content-type': 'application/json'}, 'withCredentials': true})
} else {
let encryptParam = Utils.encrypt(data);
resp = await axios.post(url, {encryptParam}, { headers:{'content-type': 'application/json'}, 'withCredentials': true})
if (resp && resp.data) {
resp.data = Utils.decrypt(resp.data);
}
}
resolve(resp);
});
}
複製代碼