springboot 基于签名的安全通信
背景:
接口安全通信:实现方案:
参考淘宝 https://open.taobao.com/doc.htm?spm=a219a.15212433.0.0.32ee669aSWyr5e&docId=101617&docType=1
推荐使用https通信,若数据包包含敏感信息,建议对数据包进行 sm4加密,可参考
https://blog.csdn.net/qq_26408545/article/details/149660016
原理:
1)双方约定签名的私钥,且一个应用一个签名的私钥
2)参数携带时间,并约定有效期
3)数据体固定格式:不能用是无序的map,且数据体内时间格式统一:"yyyy-MM-dd HH:mm:ss"
4)双方按照约定的方式进行签名与验签时间 与 签名 验证通过,方可进行业务操作
一:访问接口的实现
这里JSON序列化和摘要算法,用的hutool
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.38</version>
</dependency>
1 ApiEntity:公共的外层数据实体
import com.flowable.api.entity;import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;@Data
public class ApiEntity<T> implements Serializable {@Serialprivate static final long serialVersionUID = 1L;/*** 应用的id*/@NotEmpty(message = "应用的id不能为空")private String appId;/*** 请求最大时间误差为10分钟*/@NotNull(message = "请求时间不能为空")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;/*** 数据*/private T data;/*** 数据签名结果*/private String sign;
}
2 FlowApiController :接口实现的格式
package com.flowable.api.controller;import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.flowable.api.entity.ApiEntity;
import com.flowable.api.entity.dto.ProcessCancelDto;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/open/flow")
public class FlowApiController {/*** 取消流程实例** @param dto 请求参数*/@PostMapping("/cancel-instance")public void cancelProcessInstance(@Valid @RequestBody ApiEntity<ProcessCancelDto> dto) {// 1 验证请求参数validateParam(dto);// 2 执行流程取消// ... ;}// 因为这里 采用的hash512签名,签名密钥要大于64位public static final Map<String, String> appMap = new HashMap<>() {{// ERP 对接签名put("应用的id", "应用的签名密钥:要大于64位");}};/*** 验证请求基本格式** @param dto 请求公共参数*/public void validateParam(ApiEntity<?> dto) {// 1 计算当前时间与请求时间差,不超过10分钟long durationMinutes = LocalDateTimeUtil.between(LocalDateTimeUtil.now(), dto.getTimestamp(), ChronoUnit.MINUTES);if (Math.abs(durationMinutes) > 10L) {throw ExceptionUtil.wrapRuntime("请求时间超过许可范围!");}// 2 获取应用的 appSecretString appSecret = appMap.get(dto.getAppId());if (StrUtil.isEmpty(appSecret)) {throw ExceptionUtil.wrapRuntime("不能识别应用ID!");}// 3 配置json的日期格式JSONConfig jsonConfig = new JSONConfig();jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");// 4 假设请求存在参数,数据转json 并校验数据摘要if (ObjectUtil.isNotNull(dto.getData())) {// 1 生成签名HMac hMac = DigestUtil.hmac(HmacAlgorithm.HmacSHA512, appSecret.getBytes(StandardCharsets.UTF_8));// 2 生成请求摘要if (!StrUtil.equals(hMac.digestHex(JSONUtil.toJsonStr(dto.getData(), jsonConfig)), dto.getSign())) {throw ExceptionUtil.wrapRuntime("数据验签不通过!");}}}}
3 ProcessCancelDto:用于构建测试的实体
该实体无关紧要,你可以改成任意你的业务实体
package com.flowable.api.entity.dto;import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;/*** 管理后台 - 流程实例的创建 Request dto* !!属性顺序、属性名称、属性类型 勿动,已接入其他系统*/
@Data
public class ProcessCancelDto {/*** 用户账号*/@Size(max = 64)@NotEmpty(message = "用户账号")private String userAccount;/*** 流程实例的编号*/@NotEmpty(message = "流程实例编号不能为空")private String id;/*** 取消原因*/private String reason;
}
二:请求方的数据构建
FlowableTest 测试类:其中的 ApiEntity、ProcessCancelDto 数据格式同上
package org.example;import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import org.example.entity.ApiEntity;
import org.example.entity.dto.ProcessCancelDto;
import org.junit.Test;import java.nio.charset.StandardCharsets;
public class FlowableTest {/*** 测试流程部署*/@Testpublic void processCancelTest() {// 1 构建参数ApiEntity<ProcessCancelDto> dto = new ApiEntity<>();dto.setAppId("应用的ID");dto.setTimestamp(LocalDateTimeUtil.now());// 2 构建请求数据ProcessCancelDto processCancelDto = new ProcessCancelDto();processCancelDto.setUserAccount("账号");processCancelDto.setId("617501");processCancelDto.setReason("取消的原因");// 3 配置json序列化:指定日期格式JSONConfig jsonConfig = new JSONConfig();jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");// 4 设置数据dto.setData(processCancelDto);// 5 根据Data 生成Hash HMac hMac = DigestUtil.hmac(HmacAlgorithm.HmacSHA512, "约定的签名秘钥".getBytes(StandardCharsets.UTF_8));dto.setSign(hMac.digestHex(JSONUtil.toJsonStr(dto.getData(), jsonConfig)));// 6 打印接口请求的数据System.out.println(JSONUtil.toJsonStr(dto, jsonConfig));}
}
测试数据的构建
三:测试接口
这里用apipost 测试结果