当前位置:网站首页>Are you still using localstorage directly? The thinnest in the whole network: secondary encapsulation of local storage (including encryption, decryption and expiration processing)

Are you still using localstorage directly? The thinnest in the whole network: secondary encapsulation of local storage (including encryption, decryption and expiration processing)

2022-06-21 09:32:00 Feng Xin loves meat

background

A lot of people are using it localStorage or sessionStorage I like to use it directly , Plaintext storage , Expose information directly to ; Browser , Although it can be handled in ordinary scenes and is simple and rough , But in case of special needs , For example, set the timing function , Can't achieve . It needs to be re encapsulated , In order to increase the sense of security in use , Encryption must be indispensable . For the convenience of the project , It is specially used to encapsulate routine operation .

The structure design

In encapsulating a series of operations stored locally API Before , First, a global object is prepared , Judge the specific operation , as follows :

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

const config: globalConfig = {
    
  type: 'localStorage',              // Storage type ,localStorage | sessionStorage
  prefix: 'react-view-ui_0.0.1',     // Version number 
  expire: 24 * 60,                   // Expiration time , The default is one day , In minutes 
  isEncrypt: true,                   // Encryption support 、 Decryption data processing 
};
  1. type Indicates the storage type , by localStorage or sessionStorage ;
  2. prefix Represents the unique identifier of the view , If configured, it can be displayed in the browser view ;
  3. expire Indicates expiration time , The default is one day , In minutes ;
  4. isEncrypt Indicates support for encryption 、 Decryption data processing ;

Encryption preparation

Here is the use of crypto-js To handle encryption and decryption , You can download the package and import it first .

npm i --save-dev crypto-js

import CryptoJS from 'crypto-js';

Yes crypto-js Set the key and key offset , A private key can be passed through MD5 Encryption generation 16 Bit key acquisition .

const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); // A hexadecimal number as a key 
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); // Hexadecimal number as key offset 

encryption

const encrypt = (data: object | string): string => {
    
  // encryption 
  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();
};

Decrypt

const decrypt = (data: string) => {
    
  // Decrypt 
  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();
};

these two items. API All are stored locally value Pass as a parameter , In this way, encryption and decryption are realized .

Process the incoming data 、 It needs to be decrypted when changing ;
Encryption is required when data needs to be transferred out .

The core API Realization

setStorage Set the value

Storage The expiration time setting is not supported , To support setting expiration time , You can follow the example of Cookie How to do it ,setStorage(key, value, expire) Method , Receive three parameters , The third parameter is to set the expiration time , In relative time , Units of minutes , Type check the passed parameters . You can set a uniform expiration time , The expiration time of a single value can also be configured separately .

const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
    
  // The set value 
  if (value === '' || value === null || value === undefined) {
    
    // Null reset 
    value = null;
  }
  if (isNaN(expire) || expire < 0) {
    
    // Rationality judgment of expiration time value 
    throw new Error('Expire must be a number');
  }
  const data = {
    
    value, // Stored value 
    time: Date.now(), // Storage date 
    expire: Date.now() + 1000 * 60 * expire, // Expiration time 
  };
  // Whether encryption is required , Determine whether to load encrypted data or original data 
  window[config.type].setItem(
    autoAddPreFix(key),
    config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
  );
  return true;
};

getStorageFromKey according to key obtain value

First of all, we have to deal with key Whether there is a judgment , To prevent an error from getting a nonexistent value . Further extension of the acquisition method , It can be obtained as long as it is within the validity period Storage value , If it expires, the value will be deleted directly , And back to null.

const getStorageFromKey = (key: string) => {
    
  // Get the specified value 
  if (config.prefix) {
    
    key = autoAddPreFix(key);
  }
  if (!window[config.type].getItem(key)) {
    
    // There is no judgment 
    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) {
    
    // Expired destruction 
    removeStorageFromKey(key);
    return null;
    // No expiration return value 
  } else {
    
    return storageVal.value;
  }
};

getAllStorage Get all stored values

const getAllStorage = () => {
    
  // Get all values 
  const storageList: any = {
    };
  const keys = Object.keys(window[config.type]);
  keys.forEach((key) => {
    
    const value = getStorageFromKey(key);
    if (value !== null) {
    
      // If the value does not expire , Add to list 
      storageList[key] = value;
    }
  });
  return storageList;
};

getStorageLength Get the number of stored values

const getStorageLength = () => {
    
  // Get value list length 
  return window[config.type].length;
};

removeStorageFromKey according to key Delete stored values

const removeStorageFromKey = (key: string) => {
    
  // Delete value 
  if (config.prefix) {
    
    key = autoAddPreFix(key);
  }
  window[config.type].removeItem(key);
};

clearStorage Empty the storage list

const clearStorage = () => {
    
  window[config.type].clear();
};

autoAddPreFix Based on global configuration prefix Parameter to add prefix

const autoAddPreFix = (key: string) => {
    
  // add prefix , Keep the browser Application View uniqueness 
  const prefix = config.prefix || '';
  return `${
      prefix}_${
      key}`;
};

This is a function that does not export , Internal tool functions encapsulated as a whole , stay setStorage、getStorageFromKey、removeStorageFromKey Will use .

Export function list

Provides 6 The processing power of a function , It is sufficient to deal with most operations of the actual business .

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

Use

Use... In real business , Then import the function , Let's first look at the writer's file directory :
 Insert picture description here
The actual use :

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();

Next, take a look at the browser view :

 Insert picture description here

You can see ,key Processed and added config.prefix The prefix of , With uniqueness .
value It has been encrypted .

Take another look at the passage get Method to obtain the console value output :

 Insert picture description here

Perfect. , The actual business will clear the prefix and return it for processing , There are prefix binding and encryption processing in the view , Ensure the security of local storage .

Complete code

config.ts:

import {
     encrypt, decrypt } from './encry';
import {
     globalConfig } from './interface';

const config: globalConfig = {
    
  type: 'localStorage', // Storage type ,localStorage | sessionStorage
  prefix: 'react-view-ui_0.0.1', // Version number 
  expire: 24 * 60, // Expiration time , The default is one day , In minutes 
  isEncrypt: true, // Encryption support 、 Decryption data processing 
};

const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
    
  // The set value 
  if (value === '' || value === null || value === undefined) {
    
    // Null reset 
    value = null;
  }
  if (isNaN(expire) || expire < 0) {
    
    // Rationality judgment of expiration time value 
    throw new Error('Expire must be a number');
  }
  const data = {
    
    value, // Stored value 
    time: Date.now(), // Storage date 
    expire: Date.now() + 1000 * 60 * expire, // Expiration time 
  };
  // Whether encryption is required , Determine whether to load encrypted data or original data 
  window[config.type].setItem(
    autoAddPreFix(key),
    config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
  );
  return true;
};

const getStorageFromKey = (key: string) => {
    
  // Get the specified value 
  if (config.prefix) {
    
    key = autoAddPreFix(key);
  }
  if (!window[config.type].getItem(key)) {
    
    // There is no judgment 
    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) {
    
    // Expired destruction 
    removeStorageFromKey(key);
    return null;
    // No expiration return value 
  } else {
    
    return storageVal.value;
  }
};
const getAllStorage = () => {
    
  // Get all values 
  const storageList: any = {
    };
  const keys = Object.keys(window[config.type]);
  keys.forEach((key) => {
    
    const value = getStorageFromKey(autoRemovePreFix(key));
    if (value !== null) {
    
      // If the value does not expire , Add to list 
      storageList[autoRemovePreFix(key)] = value;
    }
  });
  return storageList;
};
const getStorageLength = () => {
    
  // Get value list length 
  return window[config.type].length;
};
const removeStorageFromKey = (key: string) => {
    
  // Delete value 
  if (config.prefix) {
    
    key = autoAddPreFix(key);
  }
  window[config.type].removeItem(key);
};
const clearStorage = () => {
    
  window[config.type].clear();
};
const autoAddPreFix = (key: string) => {
    
  // add prefix , Keep it unique 
  const prefix = config.prefix || '';
  return `${
      prefix}_${
      key}`;
};
const autoRemovePreFix = (key: string) => {
    
  // Delete prefix , Add, delete, modify, etc 
  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'); // A hexadecimal number as a key 
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); // Hexadecimal number as key offset 

const encrypt = (data: object | string): string => {
    
  // encryption 
  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) => {
    
  // Decrypt 
  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 };

summary

In front-end development, it is not only common but also unsafe to use plaintext storage locally , Secondary encapsulation of local storage can improve security , And with API Support for , It is easier to store operations locally .

原网站

版权声明
本文为[Feng Xin loves meat]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/172/202206210929095597.html