您好,登錄后才能下訂單哦!
這篇文章主要介紹如何實現Spring Boot接口參數的加密解密操作,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
加密解密本身并不是難事,問題是在何時去處理?定義一個過濾器,將請求和響應分別攔截下來進行處理也是一個辦法,這種方式雖然粗暴,但是靈活,因為可以拿到一手的請求參數和響應數據。不過 SpringMVC 中給我們提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用這兩個工具可以對請求和響應進行預處理,非常方便。
所以今天這篇文章有兩個目的:
分享參數/響應加解密的思路。
分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法。
為了讓我們開發的這個工具更加通用,也為了復習一下自定義 Spring Boot Starter,這里我們就將這個工具做成一個 stater,以后在 Spring Boot 項目中直接引用就可以。
首先我們創建一個 Spring Boot 項目,引入 spring-boot-starter-web 依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided</scope> <version>2.4.3</version> </dependency>
因為我們這個工具是為 Web 項目開發的,以后必然使用在 Web 環境中,所以這里添加依賴時 scope 設置為 provided。
依賴添加完成后,我們先來定義一個加密工具類備用,加密這塊有多種方案可以選擇,對稱加密、非對稱加密,其中對稱加密又可以使用 AES、DES、3DES 等不同算法,這里我們使用 Java 自帶的 Cipher 來實現對稱加密,使用 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)); } }
這個工具類比較簡單,不需要多解釋。需要說明的是,加密后的數據可能不具備可讀性,因此我們一般需要對加密后的數據再使用 Base64 算法進行編碼,獲取可讀字符串。換言之,上面的 AES 加密方法的返回值是一個 Base64 編碼之后的字符串,AES 解密方法的參數也是一個 Base64 編碼之后的字符串,先對該字符串進行解碼,然后再解密。
接下來我們封裝一個響應工具類備用,這個大家如果經常看松哥視頻已經很了解了:
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; } }
接下來我們定義兩個注解 @Decrypt
和 @Encrypt
:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.PARAMETER}) public @interface Decrypt { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Encrypt { }
這兩個注解就是兩個標記,在以后使用的過程中,哪個接口方法添加了 @Encrypt 注解就對哪個接口的數據加密返回,哪個接口/參數添加了 @Decrypt 注解就對哪個接口/參數進行解密。這個定義也比較簡單,沒啥好說的,需要注意的是 @Decrypt
比 @Encrypt
多了一個使用場景就是 @Decrypt
可以用在參數上。
考慮到用戶可能會自己配置加密的 key,因此我們再來定義一個 EncryptProperties 類來讀取用戶配置的 key:
@ConfigurationProperties(prefix = "spring.encrypt") public class EncryptProperties { private final static String DEFAULT_KEY = "www.itboyhub.com"; private String key = DEFAULT_KEY; public String getKey() { return key; } public void setKey(String key) { this.key = key; } }
這里我設置了默認的 key 是 www.itboyhub.com
,key 是 16 位字符串,松哥這個網站地址剛好滿足。以后如果用戶想自己配置 key,只需要在 application.properties 中配置 spring.encrypt.key=xxx
即可。
所有準備工作做完了,接下來就該正式加解密了。
因為松哥這篇文章一個很重要的目的是想和大家分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,RequestBodyAdvice 在做解密的時候倒是沒啥問題,而 ResponseBodyAdvice 在做加密的時候則會有一些局限,不過影響不大,還是我前面說的,如果想非常靈活的掌控一切,那還是自定義過濾器吧。這里我就先用這兩個工具來實現了。
另外還有一點需要注意,ResponseBodyAdvice 在你使用了 @ResponseBody 注解的時候才會生效,RequestBodyAdvice 在你使用了 @RequestBody 注解的時候才會生效,換言之,前后端都是 JSON 交互的時候,這兩個才有用。不過一般來說接口加解密的場景也都是前后端分離的時候才可能有的事。
先來看接口加密:
@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; } }
我們自定義 EncryptResponse 類實現 ResponseBodyAdvice 接口,泛型表示接口的返回類型,這里一共要實現兩個方法:
supports:這個方法用來判斷什么樣的接口需要加密,參數 returnType 表示返回類型,我們這里的判斷邏輯就是方法是否含有 @Encrypt
注解,如果有,表示該接口需要加密處理,如果沒有,表示該接口不需要加密處理。
beforeBodyWrite:這個方法會在數據響應之前執行,也就是我們先對響應數據進行二次處理,處理完成后,才會轉成 json 返回。我們這里的處理方式很簡單,RespBean 中的 status 是狀態碼就不用加密了,另外兩個字段重新加密后重新設置值即可。
另外需要注意,自定義的 ResponseBodyAdvice 需要用 @ControllerAdvice
注解來標記。
再來看接口解密:
@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); } }
首先大家注意,DecryptRequest 類我們沒有直接實現 RequestBodyAdvice
接口,而是繼承自 RequestBodyAdviceAdapter 類,該類是 RequestBodyAdvice 接口的子類,并且實現了接口中的一些方法,這樣當我們繼承自 RequestBodyAdviceAdapter 時,就只需要根據自己實際需求實現某幾個方法即可。
supports:該方法用來判斷哪些接口需要處理接口解密,我們這里的判斷邏輯是方法上或者參數上含有 @Decrypt
注解的接口,處理解密問題。
beforeBodyRead:這個方法會在參數轉換成具體的對象之前執行,我們先從流中加載到數據,然后對數據進行解密,解密完成后再重新構造 HttpInputMessage 對象返回。
接下來,我們再來定義一個自動化配置類,如下:
@Configuration @ComponentScan("org.javaboy.encrypt.starter") public class EncryptAutoConfiguration { }
這個也沒啥好說的,比較簡單。
最后,resources 目錄下定義 META-INF,然后再定義 spring.factories 文件,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.javaboy.encrypt.starter.autoconfig.EncryptAutoConfiguration
這樣當項目啟動時,就會自動加載該配置類。
至此,我們的 starter 就開發完成啦。
我們可以將項目安裝到本地倉庫,也可以發布到線上供他人使用。
安裝到本地倉庫比較簡單,直接 mvn install
,或者在 IDEA 中,點擊右邊的 Maven,然后雙擊 install,如下:
發不到線上我們可以使用 JitPack 來做。
首先我們在 GitHub 上創建一個倉庫,將我們的代碼上傳上去,這個過程應該不用我多說吧。
上傳成功后,點擊右邊的 Create a new release
按鈕,發布一個正式版,如下:
發布成功后,打開 jitpack,輸入倉庫的完整路徑,點擊 lookup 按鈕,查找到之后,再點擊 Get it
按鈕完成構建,如下:
構建成功后,JitPack 上會給出項目引用方式:
注意引用時將 tag 改成你具體的版本號。
至此,我們的工具就已經成功發布了!小伙伴們可以通過如下方式引用這個 starter:
<dependencies> <dependency> <groupId>com.github.lenve</groupId> <artifactId>encrypt-spring-boot-starter</artifactId> <version>0.0.3</version> </dependency> </dependencies> <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
我們創建一個普通的 Spring Boot 項目,引入 web 依賴,再引入我們剛剛的 starter 依賴,如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.github.lenve</groupId> <artifactId>encrypt-spring-boot-starter</artifactId> <version>0.0.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
然后再創建一個實體類備用:
public class User { private Long id; private String username; //省略 getter/setter }
創建兩個測試接口:
@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); } }
第一個接口使用了 @Encrypt
注解,所以會對該接口的數據進行加密(如果不使用該注解就不加密),第二個接口使用了 @Decrypt
所以會對上傳的參數進行解密,注意 @Decrypt
注解既可以放在方法上也可以放在參數上。
接下來啟動項目進行測試。
首先測試 get 請求接口:
可以看到,返回的數據已經加密。
再來測試 post 請求:
可以看到,參數中的加密數據已經被還原了。
如果用戶想要修改加密密鑰,可以在 application.properties 中添加如下配置:
spring.encrypt.key=1234567890123456
加密數據到了前端,前端也有一些 js 工具來處理加密數據,這個松哥后面有空再和大家說說 js 的加解密。
以上是“如何實現Spring Boot接口參數的加密解密操作”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。