前言
我们公司鉴权走的是JWT,
但是有些数据走Cookie更方便通讯,
纵观今天,网上一大把说Cookie不好的文章.
但是我们还是要用,那怎么安全一丢丢呢?
Cookie的痛点
- 不同浏览器支持的数量不等,比较新的主流浏览器还好
- 信息承载量有限,一般不能超过4kb
- 容易遭受CSRF(跨站请求伪造)等攻击
聚焦场景
非敏感信息依赖cookie通讯(需要一定的安全性支持)
解决姿势
CSRF及XSS
常规姿势有这么写(请求头):
- Content Security Policy
- Referer: 判断来源ip或者url
- Token: 自定义请求头对比也是可行的
- HttpOnly: 客户端只读状态
- SameSite: Strict会完全阻止第三方cookie,lax会宽松些
加密Cookie信息
若是觉得服务端的一些限制条件还不够,
我们可以适当采取一些手段来加密我们的Cookie!
哪怕暴露了Cookie也不会直接把数据暴露!!
比如,我经手过的落地姿势有: Base64+AES+UUID!
这个方案是很直白,支持度也很好(前后端).
具体加密原理可以自行引擎了解!
我们在这里讨论姿势,前端的支持如下!
- Base64: IE10+起步就原生支持(
window.atob
,window.btoa
)- Can I Use: Base64 Encoding and Decoding
- AES: 非常流行的块加密姿势, 密文块匹配正确才能解码,破解还是相当难的,一般人玩不来!
- UUID: 简称通用唯一识别码,其实你搞个随机数或者其他都行.这里用来混淆!
- github.com/ai/nanoid : nanoid也能生成随机字符(贼快)
我们在AES的前后拼接特殊字符串或者nanoid是为了增大破解难度,
不至于Base64解码后就是Raw AES Text!!!
演示代码
此处用了Vue Cli初始化了一个项目来演示,
部分边缘情况判断就没有考虑很全了,
然而我们实际项目中,
更倾向于把这部分逻辑抽成utils或者hooks!
里面会考虑的边缘情况比较全面!
<script>
import {nanoid} from 'nanoid'
import AES from "crypto-js/aes";
import EncUTF8 from "crypto-js/enc-utf8";
export default {
name: "App",
data() {
return {
info: Object.freeze({
version: "v1.0.0",
EngineCode: "312312fasdfasrewrwe",
}), // 需要加密的文本
tempInfo: {}, // 中间变量,缓存转换的
};
},
methods: {
isJSON(str) {
if (!str) return false;
if (typeof str === "string") {
try {
let obj = JSON.parse(str);
if (typeof obj === "object" && obj) {
return true;
} else {
return false;
}
} catch (e) {
console.log("error:" + str + "!!!" + e);
return false;
}
}
},
},
created() {
console.time('nanoid 8位数生成时间')
console.log(nanoid(8));
console.timeEnd('nanoid 8位数生成时间')
console.time('加密时间')
console.log('%c 🍩 加密前信息: ', 'font-size:20px;background-color: #33A5FF;color:#fff;', this.info);
// aes加密后的纯文本
let encryptText = AES.encrypt(JSON.stringify(this.info), "cqmyg").toString();
console.log('%c 🍩 encryptText: ', 'font-size:20px;background-color: #33A5FF;color:#fff;', encryptText);
// 拼接八位数的随机字符作为前缀(用来混淆)
let UUIDText = `${nanoid(8)}${encryptText}`
console.log('%c 🥡 UUIDText: ', 'font-size:20px;background-color: #42b983;color:#fff;', UUIDText);
// base64
this.tempInfo = window.btoa(UUIDText);
console.log(
"%c 🌰 AES+NanoID+Base64加密后的文本: ",
"font-size:20px;background-color: #EA7E5C;color:#fff;",
this.tempInfo
);
console.timeEnd('加密时间')
},
mounted() {
console.time("解密时间");
if(this.tempInfo && typeof this.tempInfo === 'string'){
let aesText = window.atob(this.tempInfo).slice(8);
console.log('%c 🥫 aesText: ', 'font-size:20px;background-color: #ED9EC7;color:#fff;', aesText);
let infoText = AES.decrypt(aesText, "cqmyg").toString(EncUTF8);
console.log('%c 🍨 infoText: ', 'font-size:20px;background-color: #E41A6A;color:#fff;', infoText);
let rawInfo = this.isJSON(infoText) ? JSON.parse(infoText): infoText;
console.log('%c 🍐 解密得出的信息: ', 'font-size:20px;background-color: #EA7E5C;color:#fff;', rawInfo);
}
console.timeEnd("解密时间");
},
render(){
return <div class="app">
我是app
</div>
}
};
</script>
测试结果
多次测试下,浏览器端平均时长均在5ms以下,
但是我觉得主流浏览器波动都不会相差太大,
顶多20ms内,这个时间是可以接受的~
若是你还是觉得有点长,
可以去掉base64和nanoid,
只保留AES加解密!!
结语
加密cookie信息,其实还是挺重要的.
若是条件允许的情况下,安全第一,性能第二!
假如您有更好的姿势,
亦或者文中有错误之处请指出,
会及时调整修正,谢谢阅读!