前后端通讯:非敏感信息Cookie的"强化"之路

626 阅读2分钟

前言

我们公司鉴权走的是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)
  • AES: 非常流行的块加密姿势, 密文块匹配正确才能解码,破解还是相当难的,一般人玩不来!
  • UUID: 简称通用唯一识别码,其实你搞个随机数或者其他都行.这里用来混淆!

我们在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信息,其实还是挺重要的.
若是条件允许的情况下,安全第一,性能第二!

假如您有更好的姿势,
亦或者文中有错误之处请指出,
会及时调整修正,谢谢阅读!