当前位置: 首页 > ai >正文

还在直接用localStorage么?全网最细:本地存储二次封装(含加密、解密、过期处理)

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

背景

很多人在用 localStoragesessionStorage 的时候喜欢直接用,明文存储,直接将信息暴露在;浏览器中,虽然一般场景下都能应付得了且简单粗暴,但特殊需求情况下,比如设置定时功能,就不能实现。就需要对其进行二次封装,为了在使用上增加些安全感,那加密也必然是少不了的了。为方便项目使用,特对常规操作进行封装。

结构设计

在封装一系列操作本地存储的API之前,先准备了一个全局对象,对具体的操作进行判断,如下:

interface globalConfig {type: 'localStorage' | 'sessionStorage';prefix: string;expire: number;isEncrypt: boolean;
}const config: globalConfig = {type: 'localStorage',              //存储类型,localStorage | sessionStorageprefix: 'react-view-ui_0.0.1',     //版本号expire: 24 * 60,                   //过期时间,默认为一天,单位为分钟isEncrypt: true,                   //支持加密、解密数据处理
};
  1. type 表示存储类型,为 localStoragesessionStorage
  2. prefix 表示视图唯一标识,如果配置可在浏览器视图中放在前缀显示;
  3. expire 表示过期时间,默认为一天,单位为分钟;
  4. isEncrypt 表示支持加密、解密数据处理;

加密准备工作

这里是用到了 crypto-js 来处理加密和解密,可先下载包并导入。

npm i --save-dev crypto-jsimport CryptoJS from 'crypto-js';

crypto-js 设置密钥和密钥偏移量,可以采用将一个私钥经 MD5 加密生成16位密钥获得。

const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); //十六位十六进制数作为密钥偏移量
加密
const encrypt = (data: object | string): string => {//加密if (typeof data === 'object') {try {data = JSON.stringify(data);} catch (e) {throw new Error('encrypt error' + e);}}const dataHex = CryptoJS.enc.Utf8.parse(data);const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {iv: SECRET_IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,});return encrypted.ciphertext.toString();
};
解密
const decrypt = (data: string) => {//解密const encryptedHexStr = CryptoJS.enc.Hex.parse(data);const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {iv: SECRET_IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,});const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);return decryptedStr.toString();
};

这两个API都是将获取到的本地存储的value作为参数进行传递,这样就实现了加密和解密。

在传入数据进行处理、改变的时候需要进行解密;
在数据需要传出时需要进行加密。

核心API实现

setStorage 设置值

Storage 本身是不支持过期时间设置的,要支持设置过期时间,可以效仿 Cookie 的做法,setStorage(key, value, expire) 方法,接收三个参数,第三个参数就是设置过期时间的,用相对时间,单位分钟,要对所传参数进行类型检查。可以设置统一的过期时间,也可以对单个值得过期时间进行单独配置。

const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {//设定值if (value === '' || value === null || value === undefined) {//空值重置value = null;}if (isNaN(expire) || expire < 0) {//过期时间值合理性判断throw new Error('Expire must be a number');}const data = {value, //存储值time: Date.now(), //存储日期expire: Date.now() + 1000 * 60 * expire, //过期时间};//是否需要加密,判断装载加密数据或原数据window[config.type].setItem(autoAddPreFix(key),config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),);return true;
};
getStorageFromKey 根据key获取value

首先要对 key 是否存在进行判断,防止获取不存在的值而报错。对获取方法进一步扩展,只要在有效期内就可以获取 Storage 值,如果过期则直接删除该值,并返回 null。

const getStorageFromKey = (key: string) => {//获取指定值if (config.prefix) {key = autoAddPreFix(key);}if (!window[config.type].getItem(key)) {//不存在判断return null;}const storageVal = config.isEncrypt? JSON.parse(decrypt(window[config.type].getItem(key) as string)): JSON.parse(window[config.type].getItem(key) as string);const now = Date.now();if (now >= storageVal.expire) {//过期销毁removeStorageFromKey(key);return null;//不过期回值} else {return storageVal.value;}
};
getAllStorage 获取所有存储值
const getAllStorage = () => {//获取所有值const storageList: any = {};const keys = Object.keys(window[config.type]);keys.forEach((key) => {const value = getStorageFromKey(key);if (value !== null) {//如果值没有过期,加入到列表中storageList[key] = value;}});return storageList;
};
getStorageLength 获取存储值数量
const getStorageLength = () => {//获取值列表长度return window[config.type].length;
};
removeStorageFromKey 根据key删除存储值
const removeStorageFromKey = (key: string) => {//删除值if (config.prefix) {key = autoAddPreFix(key);}window[config.type].removeItem(key);
};
clearStorage 清空存储列表
const clearStorage = () => {window[config.type].clear();
};
autoAddPreFix 基于全局配置的prefix参数添加前缀
const autoAddPreFix = (key: string) => {//添加前缀,保持浏览器Application视图唯一性const prefix = config.prefix || '';return `${prefix}_${key}`;
};

这是一个不导出的函数,作为整体封装的内部工具函数,在setStorage、getStorageFromKey、removeStorageFromKey会使用到。

导出函数列表

提供了6个函数的处理能力,足够应对实际业务的大部分操作。

export {setStorage,getStorageFromKey,getAllStorage,getStorageLength,removeStorageFromKey,clearStorage,
};

使用

在实际业务中使用,则将函数导入即可,这里先看下笔者的文件目录吧:
在这里插入图片描述
实际使用:

import {setStorage,getStorageFromKey,getAllStorage,getStorageLength,removeStorageFromKey,clearStorage
} from '../../_util/storage/config'setStorage('name', 'fx', 1)setStorage('age', { now: 18 }, 100000)setStorage('history', [1, 2, 3], 100000)console.log(getStorageFromKey('name'))removeStorageFromKey('name')console.log(getStorageFromKey('name'))console.log(getStorageLength());console.log(getAllStorage());clearStorage();

接下来看一下浏览器视图:

在这里插入图片描述

可以看到,key经过处理加入了config.prefix的前缀,有了唯一性。
value经过了加密处理。

再看一下通过get方式获取到的控制台值输出:

在这里插入图片描述

很完美,实际业务会把前缀清除返回进行处理,视图中有前缀绑定以及加密处理,保证了本地存储的安全性。

完整代码

config.ts:

import { encrypt, decrypt } from './encry';
import { globalConfig } from './interface';const config: globalConfig = {type: 'localStorage', //存储类型,localStorage | sessionStorageprefix: 'react-view-ui_0.0.1', //版本号expire: 24 * 60, //过期时间,默认为一天,单位为分钟isEncrypt: true, //支持加密、解密数据处理
};const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {//设定值if (value === '' || value === null || value === undefined) {//空值重置value = null;}if (isNaN(expire) || expire < 0) {//过期时间值合理性判断throw new Error('Expire must be a number');}const data = {value, //存储值time: Date.now(), //存储日期expire: Date.now() + 1000 * 60 * expire, //过期时间};//是否需要加密,判断装载加密数据或原数据window[config.type].setItem(autoAddPreFix(key),config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),);return true;
};const getStorageFromKey = (key: string) => {//获取指定值if (config.prefix) {key = autoAddPreFix(key);}if (!window[config.type].getItem(key)) {//不存在判断return null;}const storageVal = config.isEncrypt? JSON.parse(decrypt(window[config.type].getItem(key) as string)): JSON.parse(window[config.type].getItem(key) as string);const now = Date.now();if (now >= storageVal.expire) {//过期销毁removeStorageFromKey(key);return null;//不过期回值} else {return storageVal.value;}
};
const getAllStorage = () => {//获取所有值const storageList: any = {};const keys = Object.keys(window[config.type]);keys.forEach((key) => {const value = getStorageFromKey(autoRemovePreFix(key));if (value !== null) {//如果值没有过期,加入到列表中storageList[autoRemovePreFix(key)] = value;}});return storageList;
};
const getStorageLength = () => {//获取值列表长度return window[config.type].length;
};
const removeStorageFromKey = (key: string) => {//删除值if (config.prefix) {key = autoAddPreFix(key);}window[config.type].removeItem(key);
};
const clearStorage = () => {window[config.type].clear();
};
const autoAddPreFix = (key: string) => {//添加前缀,保持唯一性const prefix = config.prefix || '';return `${prefix}_${key}`;
};
const autoRemovePreFix = (key: string) => {//删除前缀,进行增删改查const lineIndex = config.prefix.length + 1;return key.substr(lineIndex);
};export {setStorage,getStorageFromKey,getAllStorage,getStorageLength,removeStorageFromKey,clearStorage,
};

encry.ts:

import CryptoJS from 'crypto-js';const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); //十六位十六进制数作为密钥偏移量const encrypt = (data: object | string): string => {//加密if (typeof data === 'object') {try {data = JSON.stringify(data);} catch (e) {throw new Error('encrypt error' + e);}}const dataHex = CryptoJS.enc.Utf8.parse(data);const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {iv: SECRET_IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,});return encrypted.ciphertext.toString();
};const decrypt = (data: string) => {//解密const encryptedHexStr = CryptoJS.enc.Hex.parse(data);const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {iv: SECRET_IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7,});const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);return decryptedStr.toString();
};export { encrypt, decrypt };

interface.ts:

interface globalConfig {type: 'localStorage' | 'sessionStorage';prefix: string;expire: number;isEncrypt: boolean;
}export type { globalConfig };

总结

前端开发中直接使用明文存储在本地是比较常见的一件事情同时也是不安全的一件事,对本地存储进行二次封装可以提高安全性,并且有了API的支持,可以在本地存储操作时更加简单。

http://www.xdnf.cn/news/10962.html

相关文章:

  • ARM 架构硬件新趋势:嵌入式领域的未来
  • 富文本编辑器:自己实现图片上传功能和图片粘贴上传(kindeditor)
  • 电脑蓝屏代码0x00000050怎么解决?3种方法快速解决
  • 【小程序】微信开发者工具——盘古开天
  • python怎么写二进制文件需要更新_Python文件迭代器对二进制文件的更新
  • Android系统的开机画面显示过程分析
  • ubuntu软件更新源,更改,可提高更新的速度
  • [经验技巧] 小米 MIUI V4 系统精简列表
  • mysql虚拟主机_远程连接mysql要点 虚拟主机定义与分类
  • QQ技术全攻略(原来简单的QQ,还隐藏着这么多秘密!)
  • 四个福利性在线网站分享,每一个都让你欲罢不能……
  • 单词学习-Unit2Text4-1(15年8月10日,第20天)
  • HTML5 文件上传的2种方式
  • 中国石油大学《大学英语(三)统考》第七套模拟题
  • FreeTextBox使用方法
  • 面试时最经常被问到的问题(Frenquently asked interview questions)(I)
  • cmos和ccd区别对比
  • 魔兽世界经典旧世私服搭建教程
  • Linux 网络通信瑞士军刀 nc 命令使用
  • rails + devise 用户注册登录及权限判断
  • 解析网页的request header
  • A站、B站、C站、D站、E站、F站、G站、H站、I站、J站、K站、L站、M站、N站、O站、P站、Q站、R站、S站、T站、U站、V站、W站、X站、Y站、Z站都是什么网站?Q站是什么?
  • Quality Center介绍
  • centos7安装dzzoffice和OnlyOffice 安装过程和踩坑出坑!
  • c语言中 5.1lf什么意思,期货股指lf_中金所期货指数IF、IH、IC分别是什么英文单词的缩写_期货攻略网...
  • awk从放弃到入门(9):awk数组详解
  • C#获取文件路径或者文件夹路径的方法
  • 谷歌离线地图FTP下载
  • IIS配置问题 Directory Listing Denied
  • MSDTC不可用解决办法