SpringBoot接口加密與解密的實(shí)現(xiàn)
一、對(duì)稱/非對(duì)稱加密
1、簡(jiǎn)介
對(duì)稱加密只有一個(gè)秘鑰,加密和解密都是用同一個(gè)秘鑰,所以叫做對(duì)稱加密。
非對(duì)稱加密有兩個(gè)秘鑰,一個(gè)是公鑰,一個(gè)是私鑰。非對(duì)稱的特點(diǎn)在于,公鑰加密的私鑰可以解密,但私鑰加密的,公鑰解不出來,只能驗(yàn)證是否由私鑰進(jìn)行加密
目前常見的加密方式是有兩種,一種是對(duì)稱加密(AES為代表),一種是非對(duì)稱加密(RSA為代表)
2、RSA和AES介紹
2.1 RSA
特點(diǎn):只需交換公鑰;公/秘鑰機(jī)制,公鑰加密,私鑰解密(或者私鑰加密,公鑰解密);公鑰負(fù)責(zé)加密,私鑰負(fù)責(zé)解密;私鑰負(fù)責(zé)簽名,公鑰負(fù)責(zé)驗(yàn)證
缺點(diǎn):加解密速度慢,特別是解密
2.2 AES
特點(diǎn):加解密用同一秘鑰
優(yōu)點(diǎn):速度快,效率高;
缺點(diǎn):秘鑰交換問題
3、RSA/AES組合
對(duì)稱加密(AES)的優(yōu)勢(shì)在于加密較快,**但**劣勢(shì)在于秘鑰一旦給出去就不安全了。非對(duì)稱加密(RSA)的優(yōu)勢(shì)在于安全,就算提供公鑰出去,別人也解密不了數(shù)據(jù),但劣勢(shì)是加密速度較慢
實(shí)際使用的過程中常常將兩者組合使用(AES+RSA),這樣可以安全的傳輸AES秘鑰,避免了RSA加密的慢速度
- 生成一個(gè)隨機(jī)AES秘鑰字符串
- 使用RSA公鑰加密AES秘鑰,然后再用AES秘鑰加密真正的內(nèi)容
- 把skey=加密的AES秘鑰,body=AES秘鑰加密的內(nèi)容傳過去
- 對(duì)面使用RSA私鑰解密AES秘鑰,然后用AES秘鑰解密出內(nèi)容
4、Base64編碼的作用
加密后的數(shù)據(jù)可能不具備可讀性,因此我們一般需要對(duì)加密后的數(shù)據(jù)再使用 Base64 算法進(jìn)行編碼,獲取可讀字符串。換言之,AES 或者RSA加密方法的返回值是一個(gè) Base64 編碼之后的字符串,AES或者RSA 解密方法的參數(shù)也是一個(gè) Base64 編碼之后的字符串,先對(duì)該字符串進(jìn)行解碼,然后再解密。
二、Java實(shí)現(xiàn)加解密/加驗(yàn)簽
1、全局Config
public class Config { public static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding"; public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; //必須是PKCS8格式 public static final String CLIENT_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO/8ucCgOTJ7DCPC" + "rCCL1VKDnUX61QnxwbAvpGp1/lletEIcjUouM7F0VvMHzViNLvpw7N7NBHPa+5gO" + "js68t9hKMUh+a6RTE34SWIqSDRPCzDKVWugsFb04o3vRl3rZ1z6B+QDdW7xwOhEr" + "PPoEqmjjIOjQPcU6xs0SPzSimOa1AgMBAAECgYAO5m0OBaSnerZNPhf7yVLMVbmd" + "D67MeEMjUkHuDjdlixi8BhPLqESzXtrLKg/Y0KM7D2nVh3sgSldWoIjDUzpCx8Z2" + "yHLU1K2wakMdBgEF3xeJPxxZRpP+earl0SyLTA4hMxl48uAjn/mkPgzoMgQkqyQz" + "5HOWjjsCLJFyEvqmoQJBAP5cBk0KXpHnCMgOupbi/pXDyaF1o+dCE97GaEdrV/0P" + "uwDfYDYfY3wzd1QM7C4b4MmE+SNVpC0W9PyaMONJlN0CQQDxiPiGdwX9actMNJea" + "JZ+k3BjCN+mM6Px7j/mtYcXWNZkyCXSXUBI62drZ0htenrh2qwichMlMgNJClvG6" + "Gu+5AkEA30R7q2gstrkrNh/nnMZHXcJr3DPc2QNhWayin/4TT+hc51krpJZMxxqN" + "5dMqBRcnavwzi9aCs6lxBcF6pCdUaQJANhd7uPls4PzRZ6abkQz9/LjB3rUQ29rN" + "uIpc2yR7XuawAVG2x7BJ9N4XMhLoyD75hrH1AsCGKFjtPbZ6OjiQGQJAF2DbIodC" + "uYb6eMZ8ux1Ab0wBEWWc5+iGgEVBNh22uZ/klE1/C0+KKzZhqgzaA/vPapq6dhuJ" + "sNXlJia10PwYrQ=="; public static final String CLIENT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv/LnAoDkyewwjwqwgi9VSg51F" + "+tUJ8cGwL6Rqdf5ZXrRCHI1KLjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFI" + "fmukUxN+EliKkg0TwswylVroLBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo" + "0D3FOsbNEj80opjmtQIDAQAB"; public static final String SERVER_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPGkxlAJPKR3BRxT" + "PIeB3pDv117j8XbpuEik5UIOlY3GUtAV1sad5NNDUAnP/DB80yAQ8ycm9Xdkutuo" + "f25Xlb7w0bRQNpfJlijx9eF8PsB6t63r8KAfWJlqbNHgN8AMK9P5XzVyN4YiEnUl" + "Jh/EYiwLiYzflNnmnnfRrI4nUo8fAgMBAAECgYEAvwTxm81heeV4Tcbi33/jUBG4" + "4BMzCzyA6DQp4wkiYju3tTS+Xq3seLEKcWdPxYi3YO7lODsM6j/fksrlSXXFMe1i" + "ZAF3FNuDVZPz2zdFYS8vh6kdlDHMJAUnU/POMMWJ880MQDtkwTuzH8Tao8OKcAP4" + "kc0QuG00wOrmuE+5gZECQQD9bqZkJsN+tj3+pxs57azy6B6gOqgm54/ujB+u63XU" + "rO9Sf57asgF4OfUFltaVhjlUMSrWcgp6f4HSy7hBSKJpAkEA9BeML5iDIHOgTIws" + "+ID55ELbzO7A/YtcYnUU09mkKCdonMXbXke+EhLApf5vX9ZmreoEfJCdsTnMEcQi" + "fkjkRwJBALpf2TXl2/cfhs/zjG45f+rTEVK8UFTsDklb+yDkQC87TnTZLbWfGr2T" + "wcFugDhOEXL9BYfXLiWQB6VB9Crug6ECQGEmTiFTbj0oSBCvaeauTsdO5PS3whAn" + "u2lkeBmpcfCZXsWm6hyoKTpARHTMw789Mjjd/1Mkq96xxkr76U6h7FkCQHRc2elg" + "Dh84wqHIptwa+moosVvd7aSzktuOB4CQRO10qKkSHVFuI+sl47A4KGzH/nX9ydUm" + "tpsTnQAlXwBczd4="; public static final String SERVER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxpMZQCTykdwUcUzyHgd6Q79de" + "4/F26bhIpOVCDpWNxlLQFdbGneTTQ1AJz/wwfNMgEPMnJvV3ZLrbqH9uV5W+8NG0" + "UDaXyZYo8fXhfD7Aeret6/CgH1iZamzR4DfADCvT+V81cjeGIhJ1JSYfxGIsC4mM" + "35TZ5p530ayOJ1KPHwIDAQAB"; }
2、RSA非對(duì)稱加密
import javax.crypto.Cipher; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import java.security.*; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import org.springframework.util.Base64Utils; public class RSACipher { /** * 獲取公鑰 * @param key 密鑰字符串(經(jīng)過base64編碼) * @return 公鑰 */ public static PublicKey getPublicKey(String key) throws Exception { // 按照X.509標(biāo)準(zhǔn)對(duì)其進(jìn)行編碼的密鑰 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(key.getBytes())); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // 生成公鑰 PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } /** * 獲取私鑰 * @param key 密鑰字符串(經(jīng)過base64編碼) * @return 私鑰 */ public static PrivateKey getPrivateKey(String key) throws Exception { // 按照PKCS8格式標(biāo)準(zhǔn)對(duì)其進(jìn)行編碼的密鑰,首先要將key進(jìn)行base64解碼 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Utils.decode(key.getBytes())); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // 生成私鑰 PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } /** * 加密方法 * @param publicKey 公鑰 * @param raw 待加密明文 * @return 加密后的密文 */ public static byte[] encrypt(String publicKey, byte[] raw) throws Exception { Key key = getPublicKey(publicKey); Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM); // 初始化 cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)); byte[] encryption = cipher.doFinal(raw); // 最后將加密后的數(shù)據(jù)進(jìn)行base64編碼 return Base64Utils.encode(encryption); } /** * 解密方法 * @param privateKey 私鑰 * @param enc 待解密密文 * @return 解密后的明文 */ public static byte[] decrypt(String privateKey, byte[] enc) throws Exception { Key key = getPrivateKey(privateKey); Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM); // 初始化 cipher.init(Cipher.DECRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)); // 先進(jìn)行base64解密,然后解碼 return cipher.doFinal(Base64Utils.decode(enc)); } /** * 簽名 * @param privateKey 私鑰 * @param content 要進(jìn)行簽名的內(nèi)容 * @return 簽名 */ public static String sign(String privateKey, byte[] content) { try { // privateKey進(jìn)行base64編碼,然后生成PKCS8格式私鑰 PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Utils.decode(privateKey.getBytes())); KeyFactory key = KeyFactory.getInstance("RSA"); PrivateKey priKey = key.generatePrivate(priPKCS8); // 簽名摘要算法 Signature signature = Signature.getInstance("SHA256WithRSA"); // 用私鑰初始化此對(duì)象以進(jìn)行簽名 signature.initSign(priKey); // 使用指定的字節(jié)數(shù)組更新簽名或驗(yàn)證 signature.update(content); // 獲得簽名字節(jié) byte[] signed = signature.sign(); // 進(jìn)行base64編碼返回 return new String(Base64Utils.encode(signed)); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 驗(yàn)簽 * @param publicKey 公鑰 * @param content 要驗(yàn)簽的內(nèi)容 * @param sign 簽名 * @return 驗(yàn)簽結(jié)果 */ public static boolean checkSign(String publicKey, byte[] content, String sign) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // 進(jìn)行base64解碼 byte[] encodedKey = Base64Utils.decodeFromString(publicKey); // 生成公鑰 PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); // 簽名摘要算法 Signature signature = Signature.getInstance("SHA256WithRSA"); // 用公鑰初始化簽名 signature.initVerify(pubKey); // 使用指定的字節(jié)數(shù)組更新簽名或驗(yàn)證 signature.update(content); // base64解碼后進(jìn)行驗(yàn)證 return signature.verify(Base64Utils.decodeFromString(sign)); } catch (Exception e) { e.printStackTrace(); } return false; } public static void main(String[] args) throws Exception { //客戶端代碼 String text = "hello"; //使用服務(wù)端公鑰加密 byte[] encryptText = RSACipher.encrypt(Config.SERVER_PUBLIC_KEY, text.getBytes()); System.out.println("加密后:\n" + new String(encryptText)); //使用客戶端私鑰簽名 String signature = RSACipher.sign(Config.CLIENT_PRIVATE_KEY, encryptText); System.out.println("簽名:\n" + signature); //服務(wù)端代碼 //使用客戶端公鑰驗(yàn)簽 boolean result = RSACipher.checkSign(Config.CLIENT_PUBLIC_KEY, encryptText, signature); System.out.println("驗(yàn)簽:\n" + result); //使用服務(wù)端私鑰解密 byte[] decryptText = RSACipher.decrypt(Config.SERVER_PRIVATE_KEY, encryptText); System.out.println("解密后:\n" + new String(decryptText)); } }
輸出結(jié)果
加密后:
ODdEkwo1RgRW8UMoHXPKe9Gwcp6lTCkg4P/Ra3gfkrO+Fw6pSgo0H54nMC5sYSsoUVy1wy2/QXeLSwR6Obfl7SU7DeW+XdGee83O2kgdsDQPbYFwlPYTd0cdOmWwZxtgEOIB9d5G75Iut4kci15vrhXZVtku92U+7aNwtYimSDQ=
簽名:
RL1qIScizRyu79/y+r2TN2FL/bSQDxnDj4JlDwSZM6XZR7CL7u5ZjLNHbsSYpHaCv9qKMS4ump50LyF+go05dsPjWZOvFNkgcm9LepkDP1qm8AzKdTGwlzhdBmy2397Ed8uBrQocFGj/721Y2xM/Db0nt7r54zKZkDXbMMlsd9k=
驗(yàn)簽:
true
解密后:
hello
3、AES對(duì)稱加密
import org.springframework.util.Base64Utils; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; public class AESCipher { public static SecureRandom random = new SecureRandom(); /** * 獲取隨機(jī)16位key,key必須要是10的整數(shù)倍,否則會(huì)出錯(cuò) */ public static String getRandom(int length) { StringBuilder ret = new StringBuilder(); for (int i = 0; i < length; i++) { // 輸出字母還是數(shù)字 boolean isChar = (random.nextInt(2) % 2 == 0); // 字符串 if (isChar) { // 取得大寫字母還是小寫字母 int choice = random.nextInt(2) % 2 == 0 ? 65 : 97; ret.append((char) (choice + random.nextInt(26))); } else { // 數(shù)字 ret.append(random.nextInt(10)); } } return ret.toString(); } /** * 加密方法,使用key充當(dāng)向量iv,增加加密算法的強(qiáng)度 * 更加安全 * @param key 密鑰 * @param raw 需要加密的內(nèi)容 * @return */ public static String encrypt(byte[] key, String raw) throws Exception { // 第一次加密 SecretKeySpec secretKey = new SecretKeySpec(key, "AES"); byte[] enCodeFormat = secretKey.getEncoded(); // 獲取二次加密的key SecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM); // 向量iv,增加加密算法的強(qiáng)度 IvParameterSpec iv = new IvParameterSpec(key); // 初始化加密器 cipher.init(Cipher.ENCRYPT_MODE, secondSecretKey, iv); // 加密 byte[] result = cipher.doFinal(raw.getBytes()); // 進(jìn)行base64編碼 return Base64Utils.encodeToString(result); } /** * 解密方法,使用key充當(dāng)向量iv,增加加密算法的強(qiáng)度 * @param key 密鑰 * @param enc 待解密內(nèi)容 * @return */ public static String decrypt(byte[] key, String enc) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key, "AES"); byte[] enCodeFormat = secretKey.getEncoded(); // 二次加密 SecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM); IvParameterSpec iv = new IvParameterSpec(key); // 初始化 cipher.init(Cipher.DECRYPT_MODE, secondSecretKey, iv); // 首先進(jìn)行base64解碼 byte[] bytes = Base64Utils.decodeFromString(enc); // 解密 byte[] result = cipher.doFinal(bytes); return new String(result); } public static void main(String[] args) throws Exception { //客戶端代碼 String text = "hello"; //隨機(jī)生成16位aes密鑰,也可以自己指定16位 byte[] aesKey = getRandom(16).getBytes(); String encryptText = AESCipher.encrypt(aesKey, text); System.out.println("加密后:\n" + encryptText); String decryptText = AESCipher.decrypt(aesKey, encryptText); System.out.println("解密后:\n" + decryptText); } }
輸出結(jié)果
加密后:
hwkYAF9eXj/dytmDBD30xg==
解密后:
hello
三、加解密 starter實(shí)戰(zhàn)
1、介紹
加密解密本身并不是難事,問題是在何時(shí)去處理?定義一個(gè)過濾器,將請(qǐng)求和響應(yīng)分別攔截下來進(jìn)行處理也是一個(gè)辦法,這種方式雖然粗暴,但是靈活,因?yàn)榭梢阅玫揭皇值恼?qǐng)求參數(shù)和響應(yīng)數(shù)據(jù)。不過 SpringMVC 中給我們提供了 ResponseBodyAdvice
和 RequestBodyAdvice
,利用這兩個(gè)工具可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行預(yù)處理,非常方便。
參考:
2、前期準(zhǔn)備
2.1 引入依賴
因?yàn)槲覀冞@個(gè)工具是為 Web 項(xiàng)目開發(fā)的,以后必然使用在 Web 環(huán)境中,所以這里添加依賴時(shí) scope 設(shè)置為 provided
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided</scope> <version>2.7.0</version> </dependency>
scope幾個(gè)屬性介紹
- compile:默認(rèn)值 他表示被依賴項(xiàng)目需要參與當(dāng)前項(xiàng)目的編譯,還有后續(xù)的測(cè)試,運(yùn)行周期也參與其中,是一個(gè)比較強(qiáng)的依賴。打包的時(shí)候通常需要包含進(jìn)去
- test:依賴項(xiàng)目?jī)H僅參與測(cè)試相關(guān)的工作,包括測(cè)試代碼的編譯和執(zhí)行,不會(huì)被打包,例如:junit
- runtime:表示被依賴項(xiàng)目無需參與項(xiàng)目的編譯,不過后期的測(cè)試和運(yùn)行周期需要其參與。與compile相比,跳過了編譯而已。例如JDBC驅(qū)動(dòng),適用運(yùn)行和測(cè)試階段
- provided:打包的時(shí)候可以不用包進(jìn)去,別的設(shè)施會(huì)提供。事實(shí)上該依賴?yán)碚撋峡梢詤⑴c編譯,測(cè)試,運(yùn)行等周期。相當(dāng)于compile,但是打包階段做了exclude操作
- system:從參與度來說,和provided相同,不過被依賴項(xiàng)不會(huì)從maven倉(cāng)庫(kù)下載,而是從本地文件系統(tǒng)拿。需要添加systemPath的屬性來定義路徑
2.2 封裝公共相應(yīng)類
public class RespBean { private Integer status; private String msg; private Object obj; public static RespBean build() { return new RespBean(); } public static RespBean ok(String msg) { return new RespBean(200, msg, null); } public static RespBean ok(String msg, Object obj) { return new RespBean(200, msg, obj); } public static RespBean error(String msg) { return new RespBean(500, msg, null); } public static RespBean error(String msg, Object obj) { return new RespBean(500, msg, obj); } private RespBean() { } private RespBean(Integer status, String msg, Object obj) { this.status = status; this.msg = msg; this.obj = obj; } public Integer getStatus() { return status; } public RespBean setStatus(Integer status) { this.status = status; return this; } public String getMsg() { return msg; } public RespBean setMsg(String msg) { this.msg = msg; return this; } public Object getObj() { return obj; } public RespBean setObj(Object obj) { this.obj = obj; return this; } }
2.3 定義加解密工具類
加密這塊有多種方案可以選擇,對(duì)稱加密、非對(duì)稱加密,其中對(duì)稱加密又可以使用 AES、DES、3DES 等不同算法,這里我們使用 Java 自帶的 Cipher 來實(shí)現(xiàn)對(duì)稱加密,使用 AES 算法
public class AESUtils { private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; // 獲取 cipher private static Cipher getCipher(byte[] key, int model) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(model, secretKeySpec); return cipher; } // AES加密 public static String encrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE); return Base64.getEncoder().encodeToString(cipher.doFinal(data)); } // AES解密 public static byte[] decrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE); return cipher.doFinal(Base64.getDecoder().decode(data)); } }
2.4 定義兩個(gè)注解
接下來我們定義兩個(gè)注解 @Decrypt
和 @Encrypt
。在以后使用的過程中,哪個(gè)接口方法添加了 @Encrypt 注解就對(duì)哪個(gè)接口的數(shù)據(jù)加密返回,哪個(gè)接口/參數(shù)添加了 @Decrypt 注解就對(duì)哪個(gè)接口/參數(shù)進(jìn)行解密。另外就是 @Decrypt
可以用在參數(shù)上
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.PARAMETER}) public @interface Decrypt { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Encrypt { }
2.5 設(shè)置自定義key
定義一個(gè) EncryptProperties 類來讀取用戶配置的 key,這樣就可以自定義key。這里設(shè)置了默認(rèn)值,以后如果用戶想自己配置 key,只需要在 application.properties 中配置 spring.encrypt.key=xxx
即可。
@ConfigurationProperties(prefix = "spring.encrypt") @Component public class EncryptProperties { // 這一塊一定要16位或者整數(shù)倍,最多256 private final static String DEFAULT_KEY = "www.shawn222.com"; private String key = DEFAULT_KEY; public String getKey() { return key; } public void setKey(String key) { this.key = key; } }
3、接口加密與解密
3.1 介紹
ResponseBodyAdvice
在你使用了 @ResponseBody
注解的時(shí)候才會(huì)生效,RequestBodyAdvice
在你使用了 @RequestBody
注解的時(shí)候才會(huì)生效,換言之,前后端都是 JSON 交互的時(shí)候,這兩個(gè)才有用
3.2 接口加密
我們自定義 EncryptResponse
類實(shí)現(xiàn) ResponseBodyAdvice
接口,泛型表示接口的返回類型,這里一共要實(shí)現(xiàn)兩個(gè)方法
- supports:這個(gè)方法用來判斷什么樣的接口需要加密,參數(shù) returnType 表示返回類型,我們這里的判斷邏輯就是方法是否含有
@Encrypt
注解,如果有,表示該接口需要加密處理,如果沒有,表示該接口不需要加密處理。 - beforeBodyWrite:這個(gè)方法會(huì)在數(shù)據(jù)響應(yīng)之前執(zhí)行,也就是我們先對(duì)響應(yīng)數(shù)據(jù)進(jìn)行二次處理,處理完成后,才會(huì)轉(zhuǎn)成 json 返回。我們這里的處理方式很簡(jiǎn)單,RespBean 中的 status 是狀態(tài)碼就不用加密了,另外兩個(gè)字段重新加密后重新設(shè)置值即可。
另外需要注意,自定義的 ResponseBodyAdvice 需要用 @ControllerAdvice
注解來標(biāo)記。
@EnableConfigurationProperties(EncryptProperties.class) @ControllerAdvice public class EncryptResponse implements ResponseBodyAdvice<RespBean> { private ObjectMapper om = new ObjectMapper(); @Autowired EncryptProperties encryptProperties; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.hasMethodAnnotation(Encrypt.class); } @Override public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { byte[] keyBytes = encryptProperties.getKey().getBytes(); try { if (body.getMsg()!=null) { body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes)); } if (body.getObj() != null) { body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes)); } } catch (Exception e) { e.printStackTrace(); } return body; } }
3.3 接口解密
首先大家注意,DecryptRequest 類我們沒有直接實(shí)現(xiàn) RequestBodyAdvice
接口,而是繼承自 RequestBodyAdviceAdapter 類,該類是 RequestBodyAdvice 接口的子類,并且實(shí)現(xiàn)了接口中的一些方法,這樣當(dāng)我們繼承自 RequestBodyAdviceAdapter 時(shí),就只需要根據(jù)自己實(shí)際需求實(shí)現(xiàn)某幾個(gè)方法即可。
- supports:該方法用來判斷哪些接口需要處理接口解密,我們這里的判斷邏輯是方法上或者參數(shù)上含有
@Decrypt
注解的接口,處理解密問題。 - beforeBodyRead:這個(gè)方法會(huì)在參數(shù)轉(zhuǎn)換成具體的對(duì)象之前執(zhí)行,我們先從流中加載到數(shù)據(jù),然后對(duì)數(shù)據(jù)進(jìn)行解密,解密完成后再重新構(gòu)造 HttpInputMessage 對(duì)象返回。
@EnableConfigurationProperties(EncryptProperties.class) @ControllerAdvice public class DecryptRequest extends RequestBodyAdviceAdapter { @Autowired EncryptProperties encryptProperties; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class); } @Override public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { byte[] body = new byte[inputMessage.getBody().available()]; inputMessage.getBody().read(body); try { byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes()); final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt); return new HttpInputMessage() { @Override public InputStream getBody() throws IOException { return bais; } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }; } catch (Exception e) { e.printStackTrace(); } return super.beforeBodyRead(inputMessage, parameter, targetType, converterType); } }
4、打包發(fā)布starter
4.1 定義自動(dòng)化配置類
// 換成自己的包路徑 @Configuration @ComponentScan("com.example.encryption") public class EncryptAutoConfiguration { }
最后,resources
目錄下定義 META
-INF,然后再定義 spring.factories
文件,這樣當(dāng)項(xiàng)目啟動(dòng)時(shí),就會(huì)自動(dòng)加載該配置類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.encryption.EncryptAutoConfiguration
安裝到本地倉(cāng)庫(kù)比較簡(jiǎn)單,直接 mvn install
,或者在 IDEA 中,點(diǎn)擊右邊的 Maven,然后雙擊 install
4.2 發(fā)布線上使用
發(fā)不到線上我們可以使用 JitPack來做。首先我們?cè)?GitHub 上創(chuàng)建一個(gè)倉(cāng)庫(kù),將我們的代碼上傳上去,上傳成功后,點(diǎn)擊右邊的 Create a new release
按鈕,發(fā)布一個(gè)正式版
發(fā)布成功后,打開 jitpack,輸入倉(cāng)庫(kù)的完整路徑,點(diǎn)擊 lookup 按鈕,查找到之后,再點(diǎn)擊 Get it
按鈕完成構(gòu)建,構(gòu)建成功后,JitPack 上會(huì)給出項(xiàng)目引用方式,新建項(xiàng)目時(shí)引入即可
5、新項(xiàng)目使用
創(chuàng)建實(shí)體類
public class User { private Long id; private String username; //省略 getter/setter }
創(chuàng)建測(cè)試類,第一個(gè)接口使用了 @Encrypt
注解,所以會(huì)對(duì)該接口的數(shù)據(jù)進(jìn)行加密(如果不使用該注解就不加密),第二個(gè)接口使用了 @Decrypt
所以會(huì)對(duì)上傳的參數(shù)進(jìn)行解密,注意 @Decrypt
注解既可以放在方法上也可以放在參數(shù)上。
@RestController public class HelloController { @GetMapping("/user") @Encrypt public RespBean getUser() { User user = new User(); user.setId((long) 99); user.setUsername("javaboy"); return RespBean.ok("ok", user); } @PostMapping("/user") public RespBean addUser(@RequestBody @Decrypt User user) { System.out.println("user = " + user); return RespBean.ok("ok", user); } }
參考文章
如何優(yōu)雅的實(shí)現(xiàn) SpringBoot 接口參數(shù)加密解密?
為什么使用 Java Cipher 要指定轉(zhuǎn)換模式?
到此這篇關(guān)于SpringBoot接口加密與解密的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot接口加密與解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Spring?Cache實(shí)現(xiàn)Caffeine+Redis二級(jí)緩存
本文主要介紹了基于Spring?Cache實(shí)現(xiàn)Caffeine+Redis二級(jí)緩存,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Spring MVC數(shù)據(jù)綁定概述及原理詳解
這篇文章主要介紹了Spring MVC數(shù)據(jù)綁定概述及原理詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Java實(shí)現(xiàn)簡(jiǎn)易學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)易學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07Java實(shí)現(xiàn)經(jīng)典游戲復(fù)雜迷宮
這篇文章主要介紹了如何利用java語言實(shí)現(xiàn)經(jīng)典《復(fù)雜迷宮》游戲,文中采用了swing技術(shù)進(jìn)行了界面化處理,感興趣的小伙伴可以動(dòng)手試一試2022-02-02Spring Security基本架構(gòu)與初始化操作流程詳解
這篇文章主要介紹了Spring Security基本架構(gòu)與初始化操作流程,Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架2023-03-03在Java的Hibernate框架中對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)進(jìn)行查詢操作
這篇文章主要介紹了Java的Hibernate框架中對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)進(jìn)行查詢操作的方法,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12