前言:
在公司協作開發的過程中,自己的項目是公共調用平臺,也可以說是中轉平臺,供公司其他團隊的項目進行接口調用。因為是不同團隊項目之間的相互調用,所以不能通過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,根據你的業務需求調用校驗方法
到此結束!!!