一文教你如何使用AES對(duì)接口參數(shù)進(jìn)行加密
前言
我們作為程序猿,在瀏覽網(wǎng)站的時(shí)候偶爾也會(huì)打開(kāi)控制臺(tái)看看請(qǐng)求的接口,我們會(huì)發(fā)現(xiàn)有些接口的傳輸是 "亂碼" ,那么這個(gè)亂碼究竟是什么呢?為什么要這么做?
其實(shí)這個(gè)所謂的 "亂碼" 其實(shí)是一種加密后的密文,其原理是前后端提前約定好一種協(xié)議,在該協(xié)議下進(jìn)行加解密的處理,例如:前端將數(shù)據(jù)加密后發(fā)送給后端,后端接收到參數(shù)后,第一時(shí)間先在約定好的協(xié)議下將密文解密成可識(shí)別的對(duì)象,再進(jìn)行邏輯處理,最后將結(jié)果加密返回給前端,前端獲取到密文后,同樣依照約定好的協(xié)議對(duì)密文進(jìn)行解密,最后將解密出來(lái)的數(shù)據(jù)拿來(lái)使用。
那么我們想實(shí)現(xiàn)同樣的效果,應(yīng)該如何做呢?別急,聽(tīng)哥們給你一一道來(lái)。
介紹
一般來(lái)說(shuō)加密算法會(huì)分為兩種:對(duì)稱(chēng)加密算法 和 非對(duì)稱(chēng)加密算法 。
對(duì)稱(chēng)加密算法
摘自百度百科: 采用單鑰密碼系統(tǒng)的加密方法,同一個(gè)密鑰可以同時(shí)用作信息的加密和解密,這種加密方法稱(chēng)為對(duì)稱(chēng)加密,也稱(chēng)為單密鑰加密。
非對(duì)稱(chēng)加密算法
摘自百度百科: 不對(duì)稱(chēng)加密算法使用兩把完全不同但又是完全匹配的一對(duì)鑰匙—公鑰和私鑰。在使用不對(duì)稱(chēng)加密算法加密文件時(shí),只有使用匹配的一對(duì)公鑰和私鑰,才能完成對(duì)明文的加密和解密過(guò)程。
經(jīng)過(guò)百度百科中的簡(jiǎn)單概要,我們已經(jīng)知道了對(duì)稱(chēng)加密算法 和 非對(duì)稱(chēng)加密算法 都是什么,但它們中間又有什么不同呢?早就猜到你會(huì)這么問(wèn)了,所以我已經(jīng)把它們的區(qū)別一一列出來(lái)了。
區(qū)別
密鑰
對(duì)稱(chēng)加密: 一共只有一種密鑰,并將該密鑰同時(shí)用來(lái)加解密。
非對(duì)稱(chēng)加密: 共有兩種密鑰:公鑰 和 私鑰 ,使用公鑰來(lái)加密,使用私鑰來(lái)解密。
速度
對(duì)稱(chēng)加密: 算法簡(jiǎn)單且加解密容易,所以執(zhí)行效率高、速度快。
非對(duì)稱(chēng)加密: 由于加密算法比較復(fù)雜,所以加解密的效率很低,速度遠(yuǎn)不如 對(duì)稱(chēng)加密 。
安全性
對(duì)稱(chēng)加密: 由于加解密均使用的為同一個(gè)密鑰,那么若密鑰泄露則有被破解密文的風(fēng)險(xiǎn)。
非對(duì)稱(chēng)加密: 由于使用了兩種密鑰,且公鑰是可公開(kāi)的密鑰,使用私鑰來(lái)進(jìn)行解密,消除了用戶(hù)交換密鑰的條件,極大程度上保證了數(shù)據(jù)安全。
實(shí)現(xiàn)
在這里給大家介紹一下 AES + CBC + PKCS5Padding 的加密方式,具體實(shí)現(xiàn)如下:
引入依賴(lài)
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
</dependency>編寫(xiě)密鑰荷載
注意:這里的 AES_KEY 和 AES_IV 可以自定義,但 必須是16位的。
/**
* @author Bummon
* @description 荷載
* @date 2023-08-12 10:27
*/
public class Common {
/**
* AES密鑰
*/
public static final byte[] AES_KEY = "Ct9x5IUNHlhq0siZ".getBytes();
/**
* AES偏移
*/
public static final byte[] AES_IV = "MIIBIjANBgkqhkiG".getBytes();
}編寫(xiě)AES工具類(lèi)
import com.test.constant.Common;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @author Bummon
* @description AES工具類(lèi)
* @date 2023-08-12 09:26
*/
public class AESUtils {
private static final String ALGORITHMSTR = "AES/CBC/PKCS5Padding";
/**
* @param content 加密內(nèi)容
* @return {@link String}
* @date 2023-08-12 09:27
* @author Bummon
* @description 加密
*/
public static String encrypt(String content) {
String encode = null;
try {
Cipher cipher = initCipher(Cipher.ENCRYPT_MODE);
byte[] encodeBytes = cipher.doFinal(content.getBytes());
encode = Base64.encodeBase64String(encodeBytes);
} catch (Exception e) {
e.printStackTrace();
}
return encode;
}
public static String decrypt(String encryptStr) {
String decode = null;
try {
Cipher cipher = initCipher(Cipher.DECRYPT_MODE);
byte[] encodeBytes = Base64.decodeBase64(encryptStr);
byte[] decodeBytes = cipher.doFinal(encodeBytes);
decode = new String(decodeBytes);
} catch (Exception e) {
e.printStackTrace();
}
return decode;
}
/**
* @param cipherMode 操作類(lèi)型 加密/解密
* @return {@link Cipher}
* @date 2023-08-12 09:42
* @author Bummon
* @description 初始化Cipher
*/
private static Cipher initCipher(int cipherMode) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
SecretKeySpec keySpec = new SecretKeySpec(Common.AES_KEY, "AES");
IvParameterSpec ivParam = new IvParameterSpec(Common.AES_IV);
cipher.init(cipherMode, keySpec, ivParam);
return cipher;
}
public static void main(String[] args) {
String encrypt = AESUtils.encrypt("Hello World");
String decrypt = AESUtils.decrypt(encrypt);
System.out.println(encrypt);
System.out.println(decrypt);
}
}自定義注解
該注解作用于接口上,可以對(duì)接口的加密或者解密實(shí)現(xiàn)更加粒子化的控制,默認(rèn)入?yún)⒔饷埽鰠⒓用堋?/p>
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* @author Bummon
* @description AES加解密注解
* @date 2023-08-12 09:44
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface AES {
/**
* 入?yún)⑹欠窠饷?,默認(rèn)解密
*/
boolean inDecode() default true;
/**
* 出參是否加密,默認(rèn)加密
*/
boolean outEncode() default true;
}DecodeRequestBodyAdvice
import com.test.anno.AES;
import com.test.util.pwd.AESUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* @author Bummon
* @date 2023-08-12 10:22
* @description 請(qǐng)求數(shù)據(jù)解密
*/
@Slf4j
@ControllerAdvice(basePackages = "com.test.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
try {
boolean encode = false;
if (methodParameter.getMethod().isAnnotationPresent(AES.class)) {
//獲取注解配置的包含和去除字段
AES aes = methodParameter.getMethodAnnotation(AES.class);
//入?yún)⑹欠裥枰饷?
encode = aes.decode();
}
if (encode) {
log.info("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行解密");
return new MyHttpInputMessage(inputMessage);
} else {
return inputMessage;
}
} catch (Exception e) {
e.printStackTrace();
log.error("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行解密出現(xiàn)異常:" + e.getMessage());
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
this.headers = inputMessage.getHeaders();
String param = IOUtils.toString(inputMessage.getBody(), "UTF-8");
//去除請(qǐng)求數(shù)據(jù)中的轉(zhuǎn)義字符
String encryptStr = easpString(param).replace("\"", "");
String decrypt = AESUtils.decrypt(encryptStr);
this.body = IOUtils.toInputStream(decrypt, "UTF-8");
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
/**
* @param param
* @return
*/
public String easpString(String param) {
if (param != null && !param.equals("")) {
String s = "{\"param\":";
//去除param中的轉(zhuǎn)義字符
String data = param.replaceAll("\\s*|\r|\n|\t", "");
if (!data.startsWith(s)) {
throw new RuntimeException("參數(shù)【param】缺失異常!");
} else {
int closeLen = data.length() - 1;
int openLen = "{\"param\":".length();
String substring = StringUtils.substring(data, openLen, closeLen);
return substring;
}
}
return "";
}
}
}EncodeResponseBodyAdvice
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.anno.AES;
import com.test.util.pwd.AESUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author Bummon
* @date 2023-08-12 10:36
* @description 返回參數(shù)加密
*/
@Slf4j
@ControllerAdvice(basePackages = "com.test.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter methodParameter,
MediaType mediaType,
Class aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
boolean encode = false;
if (methodParameter.getMethod().isAnnotationPresent(AES.class)) {
//獲取注解配置的包含和去除字段
AES aes = methodParameter.getMethodAnnotation(AES.class);
//出參是否需要加密
encode = aes.encode();
}
if (encode) {
log.info("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行加密");
ObjectMapper objectMapper = new ObjectMapper();
try {
String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
return AESUtils.encrypt(result);
} catch (Exception e) {
e.printStackTrace();
log.error("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行解密出現(xiàn)異常:" + e.getMessage());
}
}
return body;
}
}編寫(xiě)測(cè)試控制器
import java.util.HashMap;
import java.util.Map;
/**
* @author Bummon
* @description
* @date 2023-08-12 10:37
*/
@RestController
public class TestController {
@AES(decode = false)
@GetMapping("/getSecret")
public Object getSecret() {
Map<String, Object> map = new HashMap<>();
map.put("name", "Bummon");
map.put("homeUrl", "https://www.bummon.com/");
map.put("blogUrl", "https://blog.bummon.com/");
return map;
}
@AES(encode = false)
@PostMapping("/getBySecret")
public Object getBySecret(@RequestBody Map<String, Object> map) {
return map;
}
}我們?cè)谶@里編寫(xiě)了兩個(gè)接口,其中 getSecret 接口不對(duì)入?yún)⑦M(jìn)行解密,對(duì)出參進(jìn)行加密,也就是前端傳明文,后端返回為密文。getBySecret 接口是對(duì)入?yún)⑦M(jìn)行解密,不對(duì)出參加密,也就是前端傳密文,后端返回為明文。
我們的測(cè)試思路就是先測(cè)試getSecret 接口,同時(shí)也獲取到了密文,在測(cè)試getBySecret 接口時(shí)將getSecret 接口返回的密文作為參數(shù)傳進(jìn)去。
測(cè)試

我們通過(guò)getSecret 接口拿到了密文,接下來(lái)將該密文作為參數(shù)調(diào)用getBySecret 接口。

可以看到我們成功將密文解析了出來(lái),并且對(duì)接口入?yún)](méi)有影響。
到此這篇關(guān)于一文教你如何使用AES對(duì)接口參數(shù)進(jìn)行加密的文章就介紹到這了,更多相關(guān)AES接口參數(shù)加密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例解析使用Java實(shí)現(xiàn)基本的音頻播放器的編寫(xiě)要點(diǎn)
這篇文章主要介紹了使用Java實(shí)現(xiàn)基本的音頻播放器的代碼要點(diǎn)實(shí)例分享,包括音頻文件的循環(huán)播放等功能實(shí)現(xiàn)的關(guān)鍵點(diǎn),需要的朋友可以參考下2016-01-01
Spring數(shù)據(jù)庫(kù)連接池url參數(shù)踩坑及解決
這篇文章主要介紹了Spring數(shù)據(jù)庫(kù)連接池url參數(shù)踩坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java連接postgresql數(shù)據(jù)庫(kù)代碼及maven配置方式
這篇文章主要介紹了java連接postgresql數(shù)據(jù)庫(kù)代碼及maven配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
MybatisPlus調(diào)用原生SQL的三種方法實(shí)例詳解
這篇文章主要介紹了MybatisPlus調(diào)用原生SQL的三種方法,在有些情況下需要用到MybatisPlus查詢(xún)?cè)鶶QL,MybatisPlus其實(shí)帶有運(yùn)行原生SQL的方法,我這里列舉三種,需要的朋友可以參考下2022-09-09

