写在前面
本文是记录一个老生常谈的话题:接口安全
。很早之前,在一家公司任职架构时曾做过 server/app
模式的此类场景的方案设计和落地。碍于团队执行和时间成本,对其设计和实现非常有限。几个月前,为了应对敏感数据不被网络运营商(宽带运营商、cdn运营商)等窃取,以及爬虫解析。特意设计并实现了一个接口安全的方案。本文特此做一下记录。
一、定义接口安全
接口安全应具备必要的以下几点:
- 数据安全
- 接口通过网络传输的数据无法被非信任方解析和使用
- 请求安全
- 提供的接口能够防止一定程度的恶意攻击
- 跨平台通用性
- 能方便的集成并且兼容多个平台(浏览器、移动app、server等)
- 性能
- 实现以上目标的性能保障
- 实现安全
- 实现以上功能的源代码安全,防止通过源代码获得破解
接口安全
的机制
- 实现以上功能的源代码安全,防止通过源代码获得破解
二、理论设计
- 数据安全
- 最简单的办法,使用对称或非对称加密,我这里选择
aes-256-gcm
加解密算法
- 最简单的办法,使用对称或非对称加密,我这里选择
- 请求安全
- 通过
token
、一次性认证机制保证权限安全 - 通过
Bloom Filter
加快判断认证请求的频率,阻止恶意刷接口负载
- 通过
- 跨平台通用性
- 使用
js、nodejs
组合,适应nodejs client/server
模式 。能够保证在浏览器模式
和app模式
及server模式
都能使用
- 使用
- 性能
- 减少
http
交互次数。通常情况下网络请求都是较大的时间和系统资源开销。 - 保证安全密钥长度大于
16
位前提下,减少密钥长度。缩短密钥长度可以加快加解密算法执行的速度。 - 加入原始串指纹缓存,要加密的内容先进行指纹比对,有匹配的内容直接从缓存返回,不经过算法。
- 减少
- 实现安全
- 实现
密钥森林算法
,用于掩藏混淆真实的密钥。在森林里藏一颗小草往往更有意思。 dh 密钥交换算法
,用于避免网络中间人攻击,密钥不在网络中传输,由双端自己算出来。- 代码混淆加密,混淆代码,增加理解可读难度
- 实现
三、实现设计
-
服务端(使用
nodejs server模式
)- 实现
aes-256-gcm
、dh 密钥交换
算法。 - 实现认证机制
- 实现
http
调用
- 实现
-
客户端(使用
nodejs client模式
)- 实现
aes-256-gcm
、dh 密钥交换
、密钥森林
算法。
- 实现
-
开发语言选择
typescript
-
注意:
- 尽量服务端和客户端使用同一种语言实现对应算法,不同语言之间存在细微的处理差异,会导致算法异常。
-
设计图
四、实现
注意:限于篇幅,本文只讲述双端aes加解密
和http
请求的内容,其他内容另开文章说明
- 服务端
aes
实现(在18
以上的nodejs
中自带了aes
实现),下面是基本封装。
import {BinaryLike, CipherGCM, CipherGCMTypes, CipherKey, DecipherGCM} from "crypto";
import * as crypto from "crypto";
class AES {
private algorithm: CipherGCMTypes = "aes-256-gcm";
/**
* AES对称加密函数 aesEncrypt
* @param data 待加密数据
* @param key 秘钥
* @returns 加密数据信息
*/
encrypt(data: string, key: string) {
const cipherKey: CipherKey = key
const iv: BinaryLike = crypto.randomUUID().toString()
const cipher: CipherGCM = crypto.createCipheriv(this.algorithm, cipherKey, iv);
cipher.setAutoPadding(false)
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString("hex");
return {authTag: authTag, iv: iv, data: encrypted};
}
/**
* AES对称解密函数 aesDecrypt
* @param data 解密字符
* @param key 秘钥
* @param authTag 加密tag
* @param iv 加密vi
* @returns 解密数据
*/
decrypt(data: string, key: string, authTag: string, iv: string) {
const cipherKey: CipherKey = key
const decipher: DecipherGCM = crypto.createDecipheriv(this.algorithm, cipherKey, iv);
decipher.setAutoPadding(false)
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* 随机生成32位字符
*/
createKey() {
return crypto.randomBytes(Math.ceil(32 / 2))
.toString('hex')
.slice(0, 32);
}
}
export const aes = new AES()
/**
* 加密
* @param value 加密密钥+要加密的字符串
*/
export function encryptSimple(value: string) {
const key = value.substring(0, 32)
const data = value.substring(32, value.length)
return encrypt(data, key);
}
/**
* 加密
* @param value 要加密的字符串
* @param key 加密密钥
*/
export function encrypt(value: string, key: string): string {
const encryptJson = aes.encrypt(value, key);
// 36位iv+正文+32位authTag
return encryptJson.iv + encryptJson.data + encryptJson.authTag
}
/**
* 解密
* @param value 解密密钥+要解密的字符串
*/
export function decryptSimple(value: string) {
const key = value.substring(0, 32)
const data = value.substring(32, value.length)
return decrypt(data, key);
}
/**
* 解密
* @param value 要解密的字符串
* @param key 解密密钥
*/
export function decrypt(value: string, key: string) {
const iv: string = value.substring(0, 36);
const authTag: string = value.substring(value.length - 32, value.length);
const data: string = value.substring(36, value.length - 32);
return aes.decrypt(data, key, authTag, iv);
}
/**
* 生成一个32位密钥
*/
export function createKey() {
return aes.createKey();
}
export function test(): string {
// const key = "123456789012345678901234567890qw";
const key = createKey();
console.log("key:" + key);
const str = "你好abc123"
const encryptStr = encrypt(str, key)
console.log("密文:" + encryptStr);
const deStr = decrypt(encryptStr, key)
console.log("明文:" + deStr);
return encryptStr
}
function testDec() {
let key="8353d19e6d1291b9021d5406255e4833"
let encryptStr="SN3TYJQ1RGEGNJ6F1H0H22Q3073LUHQZV547bc88db1a7b939bd06eb61c311760d397c37f817db8226e68a7e95eecdc11db81a58ed801d0362ed5b92df53e39f4dd24f4aef2ef5bb5083c8713d855a0569b9be1ea4fb65832ff5c0f7fd0b287222b7d203865cfed67bee5aedde221211b4a374577c9068092"
console.log("明文:" + decrypt(encryptStr, key));
console.log(crypto.randomUUID().toString().length)
}
// testDec()
redis
操作。安装ioredis
,下面是操作redis
的封装类
import Redis from "ioredis";
import {RedisOptions} from "ioredis/built/redis/RedisOptions";
import * as AES from "../utils/aes";
enum Topics {
TOPIC_AES_CREATE_KEY = "topic_aes_createKey",
TOPIC_AES_CREATE_KEY_NOTIFY = "topic_aes_createKey_notify",
TOPIC_AES_ENCRYPT = "topic_aes_encrypt",
TOPIC_AES_ENCRYPT_NOTIFY = "topic_aes_encrypt_notify",
TOPIC_AES_DECRYPT = "topic_aes_decrypt",
TOPIC_AES_DECRYPT_NOTIFY = "topic_aes_decrypt_notify",
/**
* aes 密钥与 userNo 绑定的 redis 键值
*/
CACHE_USER_NO_AES_KEY = "cache_user_no_aes_key",
/**
* aes 加密内容 绑定的 redis 键值
*/
CACHE_ENCRYPT_DATA_AES = "cache_encrypt_data_aes",
/**
* aes 解密内容 绑定的 redis 键值
*/
CACHE_DECRYPT_DATA_AES = "cache_decrypt_data_aes",
}
interface Client {
subscribeRedis: Redis;
pubsRedis: Redis;
ciRedis: Redis;
}
class Service {
private options: RedisOptions = {
port: 6379, // Redis port
host: 'redis的host', // Redis host
family: 4, // 4 (IPv4) or 6 (IPv6)
password: 'redis的密码',
db: 1
}
private client: Client = {
subscribeRedis: new Redis(this.options),
pubsRedis: new Redis(this.options),
ciRedis: new Redis(this.options)
}
init() {
this.client.subscribeRedis.subscribe(Topics.TOPIC_AES_CREATE_KEY, Topics.TOPIC_AES_ENCRYPT, Topics.TOPIC_AES_DECRYPT, (err, count) => {
if (err) {
console.error("Failed to subscribe: %s", err.message);
} else {
console.log(
`Redis Subscribed successfully! This client is currently subscribed to ${count} channels.`
);
}
});
// 监听发来的消息
this.client.subscribeRedis.on("message", (channel: string, message: string) => {
console.log(`channel: ${channel},message length: ${message.length}`);
// console.log(`channel: ${channel},message: ${message}`);
if (message.length <= 2) return;
let parameters = JSON.parse(message)
const userNo = parameters["userNo"];
if (channel === Topics.TOPIC_AES_CREATE_KEY) {
const refresh = parameters["refresh"];
this.createKey(userNo, refresh)
} else if (channel === Topics.TOPIC_AES_ENCRYPT) {
const valueStr = parameters["value"]
const uniqueKey = parameters["uniqueKey"]
if (valueStr == null) return;
this.encrypt(userNo, uniqueKey, valueStr)
} else if (channel === Topics.TOPIC_AES_DECRYPT) {
const valueStr = parameters["value"]
const uniqueKey = parameters["uniqueKey"]
if (valueStr == null) return;
this.decrypt(userNo, uniqueKey, valueStr)
}
});
// 监听 错误
this.client.subscribeRedis.on("error", (err) => {
console.log("response err:" + err);
});
}
/**
* 解密
* @param userNo 用户编号
* @param uniqueKey 唯一标识
* @param valueStr 要解密的内容
*/
decrypt(userNo: string, uniqueKey: string, valueStr: string) {
this.createKey(userNo, false, (aesKey: string | null) => {
if (aesKey == null) {
console.log("[decrypt error] aesKey is null", userNo)
return
}
console.log("[decrypt] ", userNo, aesKey)
const valueDecrypt = AES.decrypt(valueStr, aesKey)
// 放入缓存
this.client.ciRedis.hset(this.getCacheDecryptKey(userNo), uniqueKey, valueDecrypt)
// 全局通知
this.client.pubsRedis.publish(Topics.TOPIC_AES_DECRYPT_NOTIFY, JSON.stringify({
value: valueDecrypt,
uniqueKey: uniqueKey
}))
})
}
/**
* 加密
* @param userNo 用户编号
* @param uniqueKey 数据唯一标识
* @param valueStr 要加密的内容
*/
encrypt(userNo: string, uniqueKey: string, valueStr: string) {
this.createKey(userNo, false, (aesKey: string | null) => {
if (aesKey == null) {
console.log("[encrypt error] aesKey is null", userNo)
return
}
console.log("[encrypt] ", userNo, aesKey)
const valueEncrypt = AES.encrypt(valueStr, aesKey)
// 放入缓存
this.client.ciRedis.hset(this.getCacheEncryptKey(userNo), uniqueKey, valueEncrypt)
// 全局通知
this.client.pubsRedis.publish(Topics.TOPIC_AES_ENCRYPT_NOTIFY, JSON.stringify({
value: valueEncrypt,
uniqueKey: uniqueKey
}))
})
}
/**
* 创建/获取密钥
* @param userNo 唯一标识
* @param refresh 是否刷新key
* @param callback 回调函数
*/
createKey(userNo: string, refresh: boolean, callback?: Function): string | null {
let key: string | null = null;
if (refresh) {
key = AES.createKey()
// 刷新了 aesKey 把加密缓存全部清除
const encryptKey = this.getCacheEncryptKey(userNo)
const decryptKey = this.getCacheDecryptKey(userNo)
this.client.ciRedis.del(encryptKey, decryptKey)
console.log("[refresh aesKey] del:", encryptKey, decryptKey)
// 放到缓存
this.client.ciRedis.hset(Topics.CACHE_USER_NO_AES_KEY, userNo, key!)
// 全局通知
this.client.pubsRedis.publish(Topics.TOPIC_AES_CREATE_KEY_NOTIFY, JSON.stringify({
key: key!,
userNo: userNo
}))
if (callback != null) callback(key)
} else {
this.client.ciRedis.hget(Topics.CACHE_USER_NO_AES_KEY, userNo)
.then((value: string | null) => {
key = value
console.log("[cache get aesKey]:", userNo, key)
if (callback != null) callback(key)
});
}
console.log("[createKey] ", userNo, key)
return key
}
getCacheEncryptKey(userNo: string): string {
return Topics.CACHE_ENCRYPT_DATA_AES + "_" + userNo
}
getCacheDecryptKey(userNo: string): string {
return Topics.CACHE_DECRYPT_DATA_AES + "_" + userNo
}
}
export function redisService() {
new Service().init()
}
http api
实现,安装(express
)
/ @ts-ignore
import express from 'express';
import {createKey, decrypt, encrypt, test} from "../utils/aes";
export let router = express.Router();
/**
* 生成一个密钥
*/
router.get('/aes/createKey', function (req, res) {
resJson(res, createKey())
});
/**
* 加密数据
*/
router.get('/aes/encrypt', function (req, res) {
let value: string = req.query.value!!.toString();
let key: string = req.query.key!!.toString();
resJson(res, encrypt(value, key))
});
/**
* 解密数据
*/
router.get('/aes/decrypt', function (req, res) {
let value: string = req.query.value!!.toString();
let key: string = req.query.key!!.toString();
resJson(res, decrypt(value, key))
});
function resJson(result: any, obj?: any) {
result.setHeader('Content-Type', 'application/json');
result.json(obj);
}
- 客户端(由于缺少
nodejs server
相关依赖,安装node-polyfill-webpack-plugin
,或者根据报错信息一个个安装不同依赖,并到config.js
中声明引用。这里不展开说明)aes
实现,下面是基本封装。
import {BinaryLike, CipherGCM, CipherGCMTypes, CipherKey, DecipherGCM} from "crypto";
import * as crypto from "crypto";
class AES {
private algorithm: CipherGCMTypes = "aes-256-gcm";
/**
* AES对称加密函数 aesEncrypt
* @param data 待加密数据
* @param key 秘钥
* @returns 加密数据信息
*/
encrypt(data: string, key: string) {
const cipherKey: CipherKey = key
const iv: BinaryLike = this.getRandomString()
// const iv: BinaryLike = crypto.randomUUID().toString()
const cipher: CipherGCM = crypto.createCipheriv(this.algorithm, cipherKey, iv);
cipher.setAutoPadding(false)
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString("hex");
return {authTag: authTag, iv: iv, data: encrypted};
}
/**
* AES对称解密函数 aesDecrypt
* @param data 解密字符
* @param key 秘钥
* @param authTag 加密tag
* @param iv 加密vi
* @returns 解密数据
*/
decrypt(data: string, key: string, authTag: string, iv: string) {
const cipherKey: CipherKey = key
const decipher: DecipherGCM = crypto.createDecipheriv(this.algorithm, cipherKey, iv);
decipher.setAutoPadding(false)
//@ts-ignore
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* 随机生成32位字符
*/
createKey() {
return crypto.randomBytes(Math.ceil(32 / 2))
.toString('hex')
.slice(0, 32);
}
getRandomString(length = 36) {
let arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "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"],
num = "";
for (let i = 0; i < length; i++) {
num += arr[parseInt(String(Math.random() * 36))];
}
return num;
}
}
export const aes = new AES()
/**
* 加密
* @param value 要加密的字符串
* @param key 加密密钥
*/
export function encrypt(value: string, key: string) {
const encryptJson = aes.encrypt(value, key);
// 36位iv+正文+32位authTag
return encryptJson.iv + encryptJson.data + encryptJson.authTag
}
/**
* 解密
* @param value 要解密的字符串
* @param key 解密密钥
*/
export function decrypt(value: string, key: string) {
const iv: string = value.substring(0, 36);
const authTag: string = value.substring(value.length - 32, value.length);
const data: string = value.substring(36, value.length - 32);
return aes.decrypt(data, key, authTag, iv);
}
/**
* 生成一个32位密钥
*/
export function createKey() {
return aes.createKey();
}
五、其他应用配合使用
比如现在有一个java
服务需要接口数据加解密,可以进行如下操作:
- 将
java
程序与nodejs server服务端
使用同一个redis
。 - 写一个
AESSupport
,下面是一个具体的例子(kotlin
示例,我这里已经基本没有java
了)
package com.khl.server.support
import com.fasterxml.jackson.databind.ObjectMapper
import com.khl.server.utils.MD5Utils
import org.redisson.api.RMap
import org.redisson.api.RTopic
import org.redisson.api.RedissonClient
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component
import java.util.function.Consumer
@Scope
@Component
class AESSupport @Autowired
constructor(
private val objectMapper: ObjectMapper,
private val stringRedissonClient: RedissonClient
) {
private val logger = LoggerFactory.getLogger(javaClass)
companion object {
const val TOPIC_AES_CREATE_KEY = "topic_aes_createKey"
const val TOPIC_AES_ENCRYPT = "topic_aes_encrypt"
const val TOPIC_AES_DECRYPT = "topic_aes_decrypt"
const val TOPIC_AES_CREATE_KEY_NOTIFY = "topic_aes_createKey_notify"
const val TOPIC_AES_ENCRYPT_NOTIFY = "topic_aes_encrypt_notify"
const val TOPIC_AES_DECRYPT_NOTIFY = "topic_aes_decrypt_notify"
/**
* aes 密钥与 userNo 绑定的 redis 键值
*/
const val CACHE_USER_NO_AES_KEY = "cache_user_no_aes_key"
/**
* aes 加密内容 绑定的 redis 键值
*/
const val CACHE_ENCRYPT_DATA_AES = "cache_encrypt_data_aes"
/**
* aes 解密内容 绑定的 redis 键值
*/
const val CACHE_DECRYPT_DATA_AES = "cache_decrypt_data_aes"
}
// @PostConstruct
// fun init() {
// addListenerAESKey()
// }
// fun addListenerAESKey() {
// listener(TOPIC_AES_CREATE_KEY_NOTIFY, String::class.java) { message: String? ->
// logger.info("listenerAESKey: $message")
// }
// }
/**
* 通知 aes 给当前 user 产生新的密钥
*/
fun getAESKey(userNo: String, refresh: Boolean): String {
var aeskey: String? = null
if (refresh) {
//删除 现有的key
delCacheKey(userNo)
} else {
aeskey = getCacheKey(userNo)
}
if (!aeskey.isNullOrBlank()) return aeskey
val parameters = mutableMapOf<String, Any>()
parameters["userNo"] = userNo
parameters["refresh"] = true
publish(TOPIC_AES_CREATE_KEY, parameters)
var fag = true
var index = 0
while (fag) {
aeskey = getCacheKey(userNo)
if (aeskey.isNullOrBlank()) {
index++
Thread.sleep(100)
} else fag = false
if (index >= 10) {
fag = false
}
}
if (aeskey.isNullOrBlank()) throw RuntimeException("无法获取 AESKey userNo:$userNo")
return aeskey
}
/**
* 通知 aes 加密
*/
fun encrypt(userNo: String, value: Any): String? {
val valueStr = objectMapper.writeValueAsString(value)
val uniqueKey = getUniqueKey(userNo, valueStr)
// 先从缓存中拿
var encryptStr = getCacheEncrypt(userNo, uniqueKey)
if (!encryptStr.isNullOrBlank()) return encryptStr
val parameters = mutableMapOf<String, String>()
parameters["userNo"] = userNo
parameters["value"] = valueStr
parameters["uniqueKey"] = uniqueKey
publish(TOPIC_AES_ENCRYPT, parameters)
var fag = true
var index = 0
while (fag) {
encryptStr = getCacheEncrypt(userNo, uniqueKey)
if (encryptStr.isNullOrBlank()) {
index++
Thread.sleep(100)
} else fag = false
if (index >= 10) {
fag = false
}
}
if (encryptStr.isNullOrBlank()) throw RuntimeException("加密失败 userNo:$userNo uniqueKey:$uniqueKey")
return encryptStr
}
/**
* 通知 aes 解密
*/
fun decrypt(userNo: String, valueStr: String): String? {
val uniqueKey = getUniqueKey(userNo, valueStr)
var decryptStr = getCacheDecrypt(userNo, uniqueKey)
if (!decryptStr.isNullOrBlank()) return decryptStr
val parameters = mutableMapOf<String, String>()
parameters["userNo"] = userNo
parameters["value"] = valueStr
parameters["uniqueKey"] = uniqueKey
publish(TOPIC_AES_DECRYPT, parameters)
var fag = true
var index = 0
while (fag) {
decryptStr = getCacheDecrypt(userNo, uniqueKey)
if (decryptStr.isNullOrBlank()) {
index++
Thread.sleep(100)
} else fag = false
if (index >= 10) {
fag = false
}
}
if (decryptStr.isNullOrBlank()) throw RuntimeException("解密失败 userNo:$userNo uniqueKey:$uniqueKey")
return decryptStr
}
fun <T> decrypt(userNo: String, valueStr: String, clazz: Class<T>): T? {
val decryptStr = decrypt(userNo, valueStr)
return objectMapper.readValue(decryptStr, clazz)
}
private fun delCacheKey(userNo: String) {
val userNoAesKeyMap = getRedisMapString(CACHE_USER_NO_AES_KEY)
if (userNoAesKeyMap.containsKey(userNo)) userNoAesKeyMap.remove(userNo)
}
private fun getCacheKey(userNo: String): String? {
val userNoAesKeyMap = getRedisMapString(CACHE_USER_NO_AES_KEY)
if (userNoAesKeyMap.containsKey(userNo)) return userNoAesKeyMap[userNo].toString()
return null
}
private fun getCacheEncrypt(userNo: String, uniqueKey: String): String? {
val cacheEncryptMap = getRedisMapString("${CACHE_ENCRYPT_DATA_AES}_$userNo")
if (cacheEncryptMap.containsKey(uniqueKey)) return cacheEncryptMap[uniqueKey].toString()
return null
}
private fun getCacheDecrypt(userNo: String, uniqueKey: String): String? {
val cacheDecryptMap = getRedisMapString("${CACHE_DECRYPT_DATA_AES}_$userNo")
if (cacheDecryptMap.containsKey(uniqueKey)) return cacheDecryptMap[uniqueKey].toString()
return null
}
fun getUniqueKey(userNo: String, value: String): String {
return MD5Utils.MD5Encode(getAESKey(userNo, false) + value)
}
private fun getRedisMapString(key: String): RMap<String, String> {
return stringRedissonClient.getMap(key)
// return stringRedissonClient.getMap(key, StringCodec.INSTANCE)
}
private fun getRedisTopicString(topic: String): RTopic {
return stringRedissonClient.getTopic(topic)
// return stringRedissonClient.getTopic(topic, StringCodec.INSTANCE)
}
private fun publish(topic: String, message: Any) {
getRedisTopicString(topic).publish(objectMapper.writeValueAsString(message))
}
private fun <T> listener(topic: String, clazz: Class<T>, runnable: Consumer<T>) {
getRedisTopicString(topic).addListener(
clazz
) { _, message ->
runnable.accept(message)
}
}
}
- 上面的代码,通过监听
redis
事件和主动轮寻键值来与nodejs server服务端
通信。加密数据的缓存直接在java
程序中完成查询,都不用给nodejs server服务端
处理。 - 具体用法(直接在接口上改造,不需要动业务逻辑代码)
# 记得注入 aESSupport
@ApiOperation(value = "搜索")
@PostMapping("/search")
fun searchEncrypt(@Valid @RequestBody pageRequest: APageRequest, session: Session): String? {
logger.info("search =>>>>>>> session: ${session.userNo} || ${session.username}")
if (!pageRequest.query.isNullOrBlank()) {
// 调用 解密
pageRequest.query = aESSupport.decrypt(session.userNo, pageRequest.query!!)
}
val page = khlService.search(pageRequest)
// 调用 加密
return aESSupport.encrypt(session.userNo, page)
}
到这里基本的加解密就完成了,下面看看效果截图
本质上加解密只与数据有关,与网络协议无关(http、tcp、socket
)
可以支持以下场景:
- 通用
api
标准(http
等) - 消息中间件(
kafka、rqm
等) - 缓存(
redis
等) - 数据库(
mysql、pg
等) - 理论上只要有数据载体的程序都可以使用这个方案
评论区