Spring API 接口加密/解密

news2025/4/5 14:46:53

API 接口加密/解密

为了安全性需要对接口的数据进行加密处理,不能明文暴露数据。为此应该对接口进行加密/解密处理,对于接口的行为,分别有:

  • 入参,对传过来的加密参数解密。接口处理客户端提交的参数时候,这里统一约定对 HTTP Raw Body 提交的数据(已加密的密文),转换为 JSON 处理,这是最常见的提交方式。其他 QueryString、标准 Form、HTTP Header 的入参则不支持。
  • 出参,对返回值进行加密。接口统一返回加密后的 JSON 结果。

有人把加密结果原文输出,如下图所示:

在这里插入图片描述

在这里插入图片描述但笔者觉得那是一种反模式,而保留原有 JSON 结构更好,如下提交的 JSON。

{
    "errCode": "0",
    "data": "BQduoGH4PI+6jxgu+6S2FWu5c/vHd+041ITnCH9JulUKpPX8BvRTvBNYfP7……"
}

另外也符合既有的统一返回结果,即把data数据加密,其他codemsg等的正常显示。

系统要求:只支持 Spring + Jackson 的方案。

加密算法

加密算法需要调用方(如浏览器)与 API 接口协商好。一般采用 RSA 加密算法。虽然 RSA 没 AES 速度高,但胜在是非对称加密,AES 这种对称加密机制在这场合就不适用了(因为浏览器是不能放置任何密钥的,——除非放置非对称的公钥)。

当然,如果你设计的 API 接口给其他第三方调用而不是浏览器,可以保证密钥安全的话,那么使用 AES 也可以,包括其他摘要算法同理亦可,大家商定好算法(md5/sha1/sha256……)和盐值(Slat)即可。

该组件当前仅支持 RSA(1024bit key)。下面更多的算法在路上。

  • RSA(512/2048……)
  • AES
  • MD5/SHA1/SHA256…… with Slat

使用方式

初始化

在 YAML 配置中加入:

api:
  EncryptedBody:
    publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmkKluNutOWGmAK2U……
    privateKey: MIICdgIBADANBgkqhkiG9w0BAQ……

主要是 RSA 的公钥/私钥。然后在 Spring 配置类WebMvcConfigurer中加入:

@Value("${api.EncryptedBody.publicKey}")
private String apiPublicKey;

@Value("${api.EncryptedBody.privateKey}")
private String apiPrivateKey;

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(0, new EncryptedBodyConverter(apiPublicKey, apiPrivateKey));
}

配置要加密的数据

使用方式很简单,其实就是添加一个 Java 注解@EncryptedData到你的 Java Bean 上即可。

不过我们还是按照正儿八经的循序渐进的方式去看看。首先是解密请求的数据,我们观察这个 Spring MVC 接口声明,与一般的 JSON 提交数据方式无异,添加了注解@RequestBody,其他无须修改:

@PostMapping("/submit")
boolean jsonSubmit(@RequestBody User user);

重点是 User 这个 DTO,为了标明是加密数据,需要在这个 Bean 上声明我们自定义的注解@EncryptedData

package com.ajaxjs.api.encryptedbody;

@EncryptedData
public class User {
    private String name;
    private int age;

    // Getters and Setters
}

同时我们提交的对象不再是 User 的 JSON,而是DecodeDTO(虽然最终转换为User,成功解密的话),即:

package com.ajaxjs.api.encryptedbody;

import lombok.Data;

@Data
public class DecodeDTO {
    /**
     * Encrypted data
     */
    private String data;
}

当然你可以修改这个 DTO 为你符合的结构。提交的样子就是像:

{
    "data": "BQduoGH4PI+6jxgu+6S2FWu5c/vHd+041ITnCH9JulUKpPX8BvRTvBNYfP7……"
}

这个加密过的密文怎么来的?当然是你客户端加密后的结果。或者从下面小节说的方式,返回一段密文。

返回加密的数据

下面 Controller 方法返回一个 User 对象,没有任何修改。

@GetMapping("/user")
User User();

……

@Override
public User User() {
    User user = new User();
    user.setAge(1);
    user.setName("tom");
    
    return user;
}

我们同样需要加一个注解@EncryptedData即可对其加密。当前版本中暂不支持字段级别的加密,只支持整个对象加密。

返回结果如下:

{
    "status": 1,
    "errorCode": null,
    "message": "操作成功",
    "data": "ReSSPC34JE+O/SmLCxE5zVJb6D2tzp1f5pfQyKdjvOWkQQ+qDjcjw/2m/KPA+2+uc9kseqFryXNPIZCEfsaOCJAqzMtrXyZ0JPB1skeJxKOngS5USijsY0UZqN9hLS3O/7CBLlSGkEuyXZV//WcWDG9BpQ4TAKrlRfwM4bnCo+E="
}

添加依赖

哦~对了,别忘了添加依赖,——没单独搞 jar 包,直接 copy 代码吧~才三个类:
源码。

其中ResponseResultWrapper就是统一返回结果的类,你可以改为你项目的,——其他的没啥依赖了,——还有就是 RSA 依赖我的工具包:

<dependency>
    <groupId>com.ajaxjs</groupId>
    <artifactId>ajaxjs-util</artifactId>
    <version>1.1.8</version>
</dependency>

很小巧的,才60kb 的 jar 包——请放心食用~

实现方式

这里说说实现原理,以及一些 API 设计风格的思考。

我们这种的用法,相当于接收了 A 对象(加密的,DecodeDTO),转换为 B 对象(解密的,供控制器使用)。最简单的方式就是这样的:

@PostMapping("/submit")
boolean jsonSubmit(@RequestBody DecodeDTO dto) {
    User user = 转换函数(dto.getData());
}

但是这种方法,方法数量一多则遍地DecodeDTO,API 文档也没法写了(破坏了代码清晰度,不能反映原来代码的意图)。为此我们应该尽量采用“非入侵”的方法,所谓非入侵,就是不修改原有的代码,只做额外的“装饰”。这种手段有很多,典型如 AOP,其他同类的开源库sa-encrypt-body-spring-boot、encrypt-body-spring-boot-starter也是不约而同地使用 AOP。

然而笔者个人来说不太喜欢 AOP,可能也是不够熟悉吧——反正能不用则不用。如果不用 AOP 那应该如何做呢?笔者思考了几种方式例如 Filter、拦截器等,但最终把这个问题定位于 JSON 序列化/反序列化层面上,在执行这一步骤之前就可以做加密/解密操作了。开始以为可以修改 Jackson 全局序列化方式,但碍于全局的话感觉不太合理,更合适的是在介乎于 Spring 与 Jackson 结合的地方做修改。于是有了在的MappingJackson2HttpMessageConverter基础上扩展的 EncryptedBodyConverter,重写了read方法,在反序列化之前先做解密操作,writeInternal方法亦然。

核心方法就一个类,不足一百行代码:

import com.ajaxjs.springboot.ResponseResultWrapper;
import com.ajaxjs.util.EncodeTools;
import com.ajaxjs.util.cryptography.RsaCrypto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;
import java.lang.reflect.Type;

public class EncryptedBodyConverter extends MappingJackson2HttpMessageConverter {
    public EncryptedBodyConverter(String publicKey, String privateKey) {
        super();
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

    private final String publicKey;

    private final String privateKey;

    /**
     * 使用私钥解密字符串
     *
     * @param encryptBody 经过 Base64 编码的加密字符串
     * @param privateKey  私钥字符串,用于解密
     * @return 解密后的字符串
     */
    static String decrypt(String encryptBody, String privateKey) {
        byte[] data = EncodeTools.base64Decode(encryptBody);

        return new String(RsaCrypto.decryptByPrivateKey(data, privateKey));
    }

    /**
     * 使用公钥加密字符串
     * <p>
     * 该方法采用RSA加密算法,使用给定的公钥对一段字符串进行加密
     * 加密后的字节数组被转换为 Base64 编码的字符串,以便于传输和存储
     *
     * @param body      需要加密的原始字符串
     * @param publicKey 用于加密的公钥字符串
     * @return 加密后的 Base64 编码字符串
     */
    static String encrypt(String body, String publicKey) {
        byte[] encWord = RsaCrypto.encryptByPublicKey(body.getBytes(), publicKey);
        return EncodeTools.base64EncodeToString(encWord);
    }

    /**
     * 重写 read 方法以支持加密数据的读取
     *
     * @param type         数据类型,用于确定返回对象的类型
     * @param contextClass 上下文类,未在本方法中使用
     * @param inputMessage 包含加密数据的 HTTP 输入消息
     * @return 根据类型参数反序列化后的对象实例
     * @throws IOException                     如果读取或解析过程中发生 I/O 错误
     * @throws HttpMessageNotReadableException 如果消息无法解析为对象实例
     */
    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        Class<?> clz = (Class<?>) type;

        if (clz.getAnnotation(EncryptedData.class) != null) {
            ObjectMapper objectMapper = getObjectMapper();
            DecodeDTO decodeDTO = objectMapper.readValue(inputMessage.getBody(), DecodeDTO.class);
            String encryptBody = decodeDTO.getData();

            String decodeJson = decrypt(encryptBody, privateKey);

            return objectMapper.readValue(decodeJson, clz);
        }

        return super.read(type, contextClass, inputMessage);
    }

    @Override
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        Class<?> clz = (Class<?>) type;

        if (object instanceof ResponseResultWrapper && clz.getAnnotation(EncryptedData.class) != null) {
            ResponseResultWrapper response = (ResponseResultWrapper) object;
            Object data = response.getData();
            String json = getObjectMapper().writeValueAsString(data);
            String encryptBody = encrypt(json, publicKey);

            response.setData(encryptBody);
        }

        super.writeInternal(object, type, outputMessage);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2269040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习threejs,导入pdb格式的模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.PDBLoader pdb模型加…

Frontend - 分页(针对 python / Django )

目录 一、同个文件内&#xff08;方式一&#xff09; 1. 前端 html 2. 定义分页界面 3. 获取分页数据 4.后端根据前端分页需求&#xff0c;整理分页数据 5.显示情况 6. JsonResponse 相关知识 二、不同文件内依旧有效&#xff08;方式二&#xff0c;更优化&#xff09;…

[2474].第04节:Activiti官方画流程图方式

我的后端学习大纲 Activiti大纲 1.安装位置&#xff1a; 2.启动&#xff1a;

按照乘法分解10点结构

在行列可自由变换的平面上9点结构有1430个&#xff0c;10点结构有3908个。其中可被分解为2*5的有102个&#xff0c; 5a1*2a110a28 5a1*2a210a689 5a1*2a310a1722 5a2*2a110a172 5a2*2a210a1081 5a2*2a310a2006 5a3*2a110a275 5a3*2a210a1561 5a3*2a310a2381 5a4*2a110…

JVM实战—6.频繁YGC和频繁FGC的后果

大纲 1.JVM GC导致系统突然卡死无法访问 2.什么是Young GC什么是Full GC 3.Young GC、Old GC和Full GC的发生情况 4.频繁YGC的案例(G1解决大内存YGC过慢) 5.频繁FGC的案例(YGC存活对象S区放不下) 6.问题汇总 1.JVM GC导致系统突然卡死无法访问 (1)基于JVM运行的系统最怕…

word运行时错误‘-2147221164(80040154)’ 没有注册类的解决办法

目录 问题描述解决方案 问题描述 解决方案 打开C盘找到路径C:\Users\Administrator\AppData\Roaming\Microsoft\Word\STARTUP或者在everything中搜索“Microsoft\Word\STARTUP”删除NEWebWordAddin.dotm文件即可正确打开word。

微服务保护—Sentinel快速入门+微服务整合 示例: 黑马商城

1.微服务保护 微服务保护是确保微服务架构可靠、稳定和安全的策略与技术。 在可靠性上&#xff0c;限流是控制进入微服务的请求数量&#xff0c;防止流量过大导致服务崩溃。比如电商促销时对商品详情服务进行流量限制。熔断是当被调用的微服务故障过多或响应过慢时&#xff0c;…

屏幕时序参数详解

屏幕时序参数详解 作者&#xff1a;&#xff08;Witheart&#xff09;更新时间&#xff1a;20241231 本文详细介绍了屏幕显示时序的基本参数&#xff0c;包括水平和垂直方向的有效像素、同步信号、前肩、后肩及其总周期的定义与计算公式。同时&#xff0c;通过公式和图示&…

2024年RAG:回顾与展望

2024年&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff09;技术经历了从狂热到理性的蜕变&#xff0c;成为大模型应用领域不可忽视的关键力量。年初&#xff0c;AI的“无所不能”让市场充满乐观情绪&#xff0c;RAG被视为解决复杂问题的万能钥匙&#xff1…

webpack01

webpack是一个前端工程化的打包工具 webpack在打包的时候&#xff0c;会形成一个依赖关系图&#xff0c;关联要打包的模块&#xff0c;&#xff0c;&#xff0c;不同的模块通过不同的loader去解析&#xff0c;&#xff0c;&#xff0c;比如解析css使用 css-loader,解析js使用b…

牛客网最新1129道 Java 面试题及答案整理

前言 面试&#xff0c;跳槽&#xff0c;每天都在发生&#xff0c;而对程序员来说"金三银四"更是面试和跳槽的高峰期&#xff0c;跳槽&#xff0c;更是很常见的&#xff0c;对于每个人来说&#xff0c;跳槽的意义也各不相同&#xff0c;可能是一个人更向往一个更大的…

python版本的Selenium的下载及chrome环境搭建和简单使用

针对Python版本的Selenium下载及Chrome环境搭建和使用&#xff0c;以下将详细阐述具体步骤&#xff1a; 一、Python版本的Selenium下载 安装Python环境&#xff1a; 确保系统上已经安装了Python 3.8及以上版本。可以从[Python官方网站]下载并安装最新版本的Python&#xff0c;…

突破管理困局,驾驭变革浪潮

在瞬息万变的商业环境中&#xff0c;变革已成为企业生存和发展的必经之路。许多企业在面对激烈竞争、技术进步和市场变化时&#xff0c;都会选择或被迫进行各种形式的变革。本文将深入探讨变革管理的重要性&#xff0c;介绍常见的变革模型&#xff0c;并提供实用的策略和建议&a…

WPF编程excel表格操作

WPF编程excel表格操作 摘要NPOI安装封装代码测试代码 摘要 Excel操作几种方式 使用开源库NPOI(常用&#xff0c;操作丰富)使用Microsoft.Office.Interop.Excel COM组件(兼容性问题)使用OpenXml(效率高)使用OleDb(过时) NPOI安装 封装代码 using System; using System.IO; u…

【论文阅读笔记】SCI算法与代码 | 低照度图像增强 | 2022.4.21

目录 一 SCI 1 SCI网络结构 核心代码&#xff08;model.py&#xff09; 2 SCI损失函数 核心代码&#xff08;loss.py&#xff09; 3 实验 二 SCI效果 1 下载代码 2 运行 一 SCI &#x1f49c;论文题目&#xff1a;Toward Fast, Flexible, and Robust Low-Light Image …

wps透视数据表

1、操作 首先选中你要的行字段表格 -> 插入 -> 透视数据表 -> 拖动行值&#xff08;部门&#xff09;到下方&#xff0c;拖动值&#xff08;包裹数量、运费&#xff09;到下方 2、删除 选中整个透视数据表 -> delete 如图&#xff1a;

STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计

SMEC88ST SDK开发包下载 目前市场上很多嵌入式产品方案都是可以破解复制的&#xff0c;主要是因为方案主芯片不具备防破解的功能&#xff0c;这就导致开发者投入大量精力、财力开发的新产品一上市就被别人复制&#xff0c;到市场上的只能以价格竞争&#xff0c;最后工厂复制的产…

【电路理论四】正弦电流电路

正弦电流 正弦量是随时间按正弦规律变动的电路变量。 随时间按正弦规律变动的电流称为正弦电流。 正弦电流的瞬时值表达式&#xff1a; 称为正弦电流的三要素。 分别为振幅/幅值&#xff0c;角频率&#xff0c;初相。 幅值为正弦电流的最大值&#xff0c;恒为正。 为正弦电…

多模态论文笔记——Coca(副)

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍多模态模型Coca&#xff0c;在DALLE 3中使用其作为captioner基准模型的原因和优势。 文章目录 ALBEF论文模型结构组成训练目标 CoCa​论文模型结构CoCa…