侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

接口安全的设计与实现

Administrator
2024-04-25 / 0 评论 / 0 点赞 / 16 阅读 / 26645 字

写在前面

本文是记录一个老生常谈的话题:接口安全 。很早之前,在一家公司任职架构时曾做过 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-gcmdh 密钥交换 算法。
    • 实现认证机制
    • 实现http调用
  • 客户端(使用nodejs client模式

    • 实现aes-256-gcmdh 密钥交换 密钥森林 算法。
  • 开发语言选择typescript

  • 注意:

    • 尽量服务端和客户端使用同一种语言实现对应算法,不同语言之间存在细微的处理差异,会导致算法异常。
  • 设计图
    接口安全设计.png

四、实现

注意:限于篇幅,本文只讲述双端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)
    }

到这里基本的加解密就完成了,下面看看效果截图
iShot_2024-04-25_16.29.26.png
iShot_2024-04-25_16.32.46.png

本质上加解密只与数据有关,与网络协议无关(http、tcp、socket
可以支持以下场景:

  • 通用api标准(http等)
  • 消息中间件(kafka、rqm等)
  • 缓存(redis等)
  • 数据库(mysql、pg等)
  • 理论上只要有数据载体的程序都可以使用这个方案

个人公众号

0

评论区