springboot项目不同平台项目通过http接口AES加密传输
前言:
在公司协作开发的过程中,自己的项目是公共调用平台,也可以说是中转平台,供公司其他团队的项目进行接口调用。因为是不同团队项目之间的相互调用,所以不能通过openFeign远程调用。只能通过http远程调用,但是在http调用的过程中数据不能通过明文传输,这样是不安全的。所以下面演示如何使用AES秘钥,对接口数据进行加解密。
目录
一,引入maven依赖
二,生成AES对称秘钥
三,将控制台打印的秘钥保存,粘贴到yml文件中去
四,核心加解密工具类
五,封装统一返回对象工具类
5.1,校验重复请求工具类(可选,非必要)
六,接收加密后的数据请求接口(解密示例接口)
七,发送请求加密data业务数据(加密示例接口)
八,发送请求测试
九,升级拓展
一,引入maven依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.37</version></dependency>
二,生成AES对称秘钥
运行这一块代码输出秘钥:
@Testpublic void test() {byte[] keyBytes = SecureUtil.generateKey("AES").getEncoded();String keyStr = Base64.encode(keyBytes); // 转 Base64 字符串,方便存配置System.out.println("AES 密钥:" + keyStr);}
三,将控制台打印的秘钥保存,粘贴到yml文件中去
四,核心加解密工具类
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;@Component
public class AesKit {@Value("${aes.key}")private String key;private SymmetricCrypto aes;@PostConstructpublic void init() {this.aes = new SymmetricCrypto(SymmetricAlgorithm.AES, Base64.decode(key));}public String encrypt(String plain) {return aes.encryptBase64(plain);}public String decrypt(String cipher) {return aes.decryptStr(cipher);}
}
五,封装统一返回对象工具类
import com.zqd.common.constant.HttpStatus;
import com.zqd.common.utils.uuid.IdUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;/*** 加密data业务数据响应实体类*/
@ApiModel(value = "返回对象",description = "返回结果"
)
@Data
public class EncryptionR<T> implements Serializable {private static final long serialVersionUID = 1L;/** 成功 */public static final int SUCCESS = HttpStatus.SUCCESS;/** 失败 */public static final int FAIL = HttpStatus.ERROR;@ApiModelProperty("返回code值")private int code;@ApiModelProperty("时间戳")private Long timestamp=System.currentTimeMillis();@ApiModelProperty("随机数")//如果生成uuid工具类用不了那就切换成你自己生成uuid的工具类private String nonce= IdUtils.fastSimpleUUID();@ApiModelProperty("返回提示信息")private String msg;@ApiModelProperty("返回数据")private T data;public static <T> EncryptionR<T> ok(){return restResult(null, SUCCESS, "操作成功");}public static <T> EncryptionR<T> ok(T data){return restResult(data, SUCCESS, "操作成功");}public static <T> EncryptionR<T> ok(T data, String msg){return restResult(data, SUCCESS, msg);}public static <T> EncryptionR<T> fail(){return restResult(null, FAIL, "操作失败");}public static <T> EncryptionR<T> fail(String msg){return restResult(null, FAIL, msg);}public static <T> EncryptionR<T> fail(T data){return restResult(data, FAIL, "操作失败");}public static <T> EncryptionR<T> fail(T data, String msg){return restResult(data, FAIL, msg);}public static <T> EncryptionR<T> fail(int code, String msg){return restResult(null, code, msg);}private static <T> EncryptionR<T> restResult(T data, int code, String msg){EncryptionR<T> apiResult = new EncryptionR<>();apiResult.setCode(code);apiResult.setData(data);apiResult.setMsg(msg);return apiResult;}
}
5.1,校验重复请求工具类(可选,非必要)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.time.Duration;@Component
public class HttpRequestCheck {@Autowiredpublic RedisTemplate redisTemplate;/*** 校验重复请求* @param timestamp 时间戳* @param nonce 随机数* @return*/public void isRepeatSubmit(Long timestamp, String nonce) {long now = System.currentTimeMillis();long diff = Math.abs(now - timestamp);// 允许 60 秒误差(发送请求到接收到请求的时间差)if (diff > 60000) {throw new RuntimeException("请求已过期");}// 防止重复 nonce:Redis 查重String key = "nonce:" + nonce;if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {throw new RuntimeException("重复请求");}redisTemplate.opsForValue().set(key, "1", Duration.ofMinutes(5));}
}
六,接收加密后的数据请求接口(解密示例接口)
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.zqd.common.annotation.Anonymous;
import com.zqd.common.constant.HttpStatus;
import com.zqd.common.exception.ServiceException;
import com.zqd.system.serverutils.dome2.AesKit;
import com.zqd.system.serverutils.dome2.EncryptionR;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Objects;@RestController
@RequestMapping("/domeD")
@Api(tags = "测试D")
public class DomeTestControllerB {private static final Logger log = LoggerFactory.getLogger(DomeTestController.class);@AutowiredAesKit aesKit;// @Autowired
// HttpRequestCheck httpRequestCheck;@PostMapping("/testD")@ApiOperation(value = "内测试D")@Anonymouspublic EncryptionR queryIncompleteExamination(@RequestBody @Validated String cipher) {EncryptionR req = JSONUtil.toBean(cipher, EncryptionR.class);if(!Objects.equals(HttpStatus.SUCCESS, req.getCode())){throw new ServiceException("请求失败:"+req.getMsg());}//校验重复请求(可选)
// httpRequestCheck.isRepeatSubmit(req.getTimestamp(), req.getNonce());log.info("接收到的密文:{}", req.getData());String plain = aesKit.decrypt(req.getData().toString());log.info("解密后的明文:{}", JSON.toJSONString(plain));String resp = JSONUtil.toJsonStr("哈哈哈111111111111返回");// 直接返回其他密文字符串return EncryptionR.ok(aesKit.encrypt(resp));}}
七,发送请求加密data业务数据(加密示例接口)
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.zqd.common.annotation.Anonymous;
import com.zqd.common.constant.HttpStatus;
import com.zqd.common.exception.ServiceException;
import com.zqd.system.serverutils.dome.dome.AesKit;
import com.zqd.system.serverutils.dome.dome.EncryptionR;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Objects;@RestController
@RequestMapping("/domeE")
@Api(tags = "测试E")
public class DomeTestControllerA {@AutowiredAesKit aesKit;@PostMapping("/testE")@ApiOperation(value = "内测试E")@Anonymouspublic EncryptionR<String> queryIncompleteExamination() {// 1. 构造业务 JSONString json = JSONUtil.toJsonStr("哈哈哈哈");// 2. 加密String cipher = aesKit.encrypt(json);// 3. 发 HTTP,直接发字符串String respCipher = HttpRequest.post("http://localhost:****/game/domeD/testD")//请求地址换成你自己的目标地址url.body(JSON.toJSONString(EncryptionR.ok(cipher))).execute().body();EncryptionR bean = JSONUtil.toBean(respCipher, EncryptionR.class);if(!Objects.equals(HttpStatus.SUCCESS, bean.getCode())){throw new ServiceException("请求失败:"+bean.getMsg());}// 4. 解密 B 的返回String plainResp = aesKit.decrypt(bean.getData().toString());return EncryptionR.ok(plainResp);}}
八,发送请求测试
在DomeTestControllerA中加密业务数据发送请求到DomeTestControllerB解密数据。
DomeTestControllerA:
DomeTestControllerB:
后面DomeTestControllerB响应的业务数据也需要加密响应给DomeTestControllerA,DomeTestControllerA以相同的方式解密即可。
到此就可以实现接口数据的加解密过程了。
九,升级拓展
如果你想实现校验重复请求,打开这行代码即可。
如果觉得粒度不够,没有细粒度到用户id。那么你也可以在我的代码基础上修改。
修改步骤:
1,在HttpRequestCheck中添加属性userId
2,在HttpRequestCheck重载一个isRepeatSubmit方法,入参添加一个用户id。redsi缓存的key在随机数前面加上用户id即可
3,根据你的业务需求调用校验方法
到此结束!!!