springboot项目不同平台项目通过http接口AES加密传输

发布于:2025-08-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

前言:

在公司协作开发的过程中,自己的项目是公共调用平台,也可以说是中转平台,供公司其他团队的项目进行接口调用。因为是不同团队项目之间的相互调用,所以不能通过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对称秘钥

运行这一块代码输出秘钥:

@Test
    public 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;

    @PostConstruct
    public 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 {



    @Autowired
    public 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);

    @Autowired
    AesKit aesKit;

//    @Autowired
//    HttpRequestCheck httpRequestCheck;

    @PostMapping("/testD")
    @ApiOperation(value = "内测试D")
    @Anonymous
    public 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 {

    @Autowired
    AesKit aesKit;


    @PostMapping("/testE")
    @ApiOperation(value = "内测试E")
    @Anonymous
    public EncryptionR<String> queryIncompleteExamination() {
        // 1. 构造业务 JSON
        String 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,根据你的业务需求调用校验方法

到此结束!!!


网站公告

今日签到

点亮在社区的每一天
去签到