SpringBoot使用AES對(duì)JSON數(shù)據(jù)加密和解密的實(shí)現(xiàn)方法
1、加密解密原理
客戶端和服務(wù)端都可以加密和解密,使用base64進(jìn)行網(wǎng)絡(luò)傳輸
加密方
字符串 -> AES加密 -> base64
解密方
base64 -> AES解密 -> 字符串
2、項(xiàng)目示例
2.1、項(xiàng)目結(jié)構(gòu)
$ tree -I target -I test . ├── pom.xml └── src └── main ├── java │ └── com │ └── example │ └── demo │ ├── Application.java │ ├── annotation │ │ └── SecretData.java │ ├── config │ │ ├── CrossConfig.java │ │ ├── DecryptRequestBodyAdvice.java │ │ ├── EncryptResponseBodyAdvice.java │ │ ├── SecretConfig.java │ │ └── WebMvcConfig.java │ ├── controller │ │ ├── IndexController.java │ │ └── UserController.java │ ├── request │ │ └── JsonRequest.java │ ├── response │ │ ├── JsonResult.java │ │ └── JsonResultVO.java │ ├── service │ │ ├── SecretDataService.java │ │ └── impl │ │ └── SecretDataServiceImpl.java │ └── utils │ └── CipherUtil.java └── resources ├── application.yml ├── static │ ├── axios.min.js │ └── crypto-js.min.js └── templates └── index.html
2.2、常規(guī)業(yè)務(wù)代碼
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <mybatis-plus.version>3.5.2</mybatis-plus.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
Application.java
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
WebMvcConfig.java
package com.example.demo.config; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { // 設(shè)置靜態(tài)資源映射 @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/static/"); } }
CrossConfig.java
package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * 處理跨域問(wèn)題 */ @Configuration public class CrossConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); config.setAllowCredentials(false); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config); return new CorsFilter(configSource); } }
JsonRequest.java
package com.example.demo.request; import lombok.Data; /** * 統(tǒng)一的請(qǐng)求體數(shù)據(jù) */ @Data public class JsonRequest { /** * 未加密數(shù)據(jù) */ private Object data; /** * 加密數(shù)據(jù) */ private String encryptData; }
JsonResult.java
package com.example.demo.response; import lombok.Data; /** * 統(tǒng)一的返回體數(shù)據(jù) 不加密 */ @Data public class JsonResult<T> { private String message; private T data; private Integer code; public static <T> JsonResult success(T data){ JsonResult<T> jsonResult = new JsonResult<>(); jsonResult.setCode(0); jsonResult.setData(data); jsonResult.setMessage("success"); return jsonResult; } }
JsonResultVO.java
package com.example.demo.response; import lombok.Data; /** * 統(tǒng)一的返回體數(shù)據(jù) 加密 */ @Data public class JsonResultVO { private String message; private String encryptData; private Integer code; }
IndexController.java
package com.example.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ return "index"; } }
UserController.java
package com.example.demo.controller; import com.example.demo.annotation.SecretData; import com.example.demo.response.JsonResult; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * 對(duì)該Controller中的所有方法進(jìn)行加解密處理 */ @RestController @SecretData public class UserController { @GetMapping("/user/getUser") public JsonResult getUser() { Map<String, String> user = new HashMap<>(); user.put("name", "Tom"); user.put("age", "18"); return JsonResult.success(user); } @PostMapping("/user/addUser") public Object addUser(@RequestBody Map<String, String> data) { System.out.println(data); return data; } }
2.3、加密的實(shí)現(xiàn)
application.yml
secret: key: 1234567890123456 # 密鑰位數(shù)為16位 enabled: true # 開(kāi)啟加解密功能
SecretData.java
package com.example.demo.annotation; import org.springframework.web.bind.annotation.Mapping; import java.lang.annotation.*; /** * 只有添加有該注解的Controller類或是具體接口方法才進(jìn)行數(shù)據(jù)的加密解密 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Mapping @Documented public @interface SecretData { }
DecryptRequestBodyAdvice.java
package com.example.demo.config; import com.example.demo.annotation.SecretData; import com.example.demo.request.JsonRequest; import com.example.demo.service.SecretDataService; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.RequestBodyAdviceAdapter; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; /** * 對(duì)請(qǐng)求內(nèi)容進(jìn)行解密 * 只有開(kāi)啟了加解密功能才會(huì)生效 * 僅對(duì)使用了@RqestBody注解的生效 * https://blog.csdn.net/xingbaozhen1210/article/details/98189562 */ @ControllerAdvice // @ConditionalOnProperty(name = "secret.enabled", havingValue = "true") public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter { @Resource private SecretDataService secretDataService; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.getMethod().isAnnotationPresent(SecretData.class) || methodParameter.getMethod().getDeclaringClass().isAnnotationPresent(SecretData.class); } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { System.out.println("beforeBodyRead"); String body = inToString(inputMessage.getBody()); System.out.println(body); ObjectMapper objectMapper = new ObjectMapper(); JsonRequest jsonRequest = objectMapper.readValue(body, JsonRequest.class); // 默認(rèn)取data數(shù)據(jù),如果提交加密數(shù)據(jù)則解密 String decryptData = null; if (jsonRequest.getEncryptData() != null) { decryptData = secretDataService.decrypt(jsonRequest.getEncryptData()); } else{ decryptData = objectMapper.writeValueAsString(jsonRequest.getData()); } String data = decryptData; // 解密后的數(shù)據(jù) System.out.println(data); return new HttpInputMessage() { @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(data.getBytes()); } }; } /** * 讀取輸入流為字符串 * * @param is * @return */ private String inToString(InputStream is) { byte[] buf = new byte[10 * 1024]; int length = -1; StringBuilder sb = new StringBuilder(); try { while ((length = is.read(buf)) != -1) { sb.append(new String(buf, 0, length)); } return sb.toString(); } catch (IOException e) { throw new RuntimeException(e); } } }
EncryptResponseBodyAdvice.java
package com.example.demo.config; import com.example.demo.annotation.SecretData; import com.example.demo.response.JsonResult; import com.example.demo.response.JsonResultVO; import com.example.demo.service.SecretDataService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.beans.BeanUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; 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; import javax.annotation.Resource; /** * 對(duì)響應(yīng)內(nèi)容加密 */ @ControllerAdvice @ConditionalOnProperty(name = "secret.enabled", havingValue = "true") public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> { @Resource private SecretDataService secretDataService; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.getMethod().isAnnotationPresent(SecretData.class) || returnType.getMethod().getDeclaringClass().isAnnotationPresent(SecretData.class); } @SneakyThrows @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { System.out.println("beforeBodyWrite"); // 僅對(duì)JsonResult對(duì)象數(shù)據(jù)加密 if (body instanceof JsonResult) { JsonResult jsonResult = (JsonResult) body; JsonResultVO jsonResultVO = new JsonResultVO(); BeanUtils.copyProperties(jsonResult, jsonResultVO); String jsonStr = new ObjectMapper().writeValueAsString(jsonResult.getData()); jsonResultVO.setEncryptData(secretDataService.encrypt(jsonStr)); return jsonResultVO; } else { return body; } } }
SecretConfig.java
package com.example.demo.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "secret") public class SecretConfig { private Boolean enabled; private String key; public Boolean getEnabled() { return enabled; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } }
SecretDataService.java
package com.example.demo.service; /** * 加密解密的接口 */ public interface SecretDataService { /** * 數(shù)據(jù)加密 * * @param data 待加密數(shù)據(jù) * @return String 加密結(jié)果 */ String encrypt(String data); /** * 數(shù)據(jù)解密 * * @param data 待解密數(shù)據(jù) * @return String 解密后的數(shù)據(jù) */ String decrypt(String data); }
SecretDataServiceImpl.java
package com.example.demo.service.impl; import com.example.demo.config.SecretConfig; import com.example.demo.service.SecretDataService; import com.example.demo.utils.CipherUtil; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * 具體的加解密實(shí)現(xiàn) */ @Service public class SecretDataServiceImpl implements SecretDataService { @Resource private SecretConfig secretConfig; @Override public String decrypt(String data) { return CipherUtil.decrypt(secretConfig.getKey(), data); } @Override public String encrypt(String data) { return CipherUtil.encrypt(secretConfig.getKey(), data); } }
CipherUtil.java
package com.example.demo.utils; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.util.Base64; /** * 數(shù)據(jù)加密解密工具類 * 加密后返回base64 */ public class CipherUtil { /** * 定義加密算法 */ private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; /** * 解密 * * @param secretKey * @param cipherText base64 * @return */ public static String decrypt(String secretKey, String cipherText) { // 將Base64編碼的密文解碼 byte[] encrypted = Base64.getDecoder().decode(cipherText); try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, key); return new String(cipher.doFinal(encrypted)); } catch (Exception e) { throw new RuntimeException(e); } } /** * 加密 * * @param secretKey * @param plainText base64 * @return */ public static String encrypt(String secretKey, String plainText) { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, key); return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes(Charset.forName("UTF-8")))); } catch (Exception e) { throw new RuntimeException(e); } } }
瀏覽器中實(shí)現(xiàn)加密解密
templates/index.html
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>接口數(shù)據(jù)加密解密</title> </head> <body> <!-- 引入依賴 --> <!-- <script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> --> <script src="/static/crypto-js.min.js"></script> <!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.min.js"></script> --> <script src="/static/axios.min.js"></script> <h1>查看控制臺(tái)</h1> <!-- 加密解密模塊 --> <script type="text/javascript"> const SECRET_KEY = "1234567890123456"; /** * 加密方法 * @param data 待加密數(shù)據(jù) * @returns {string|*} */ function encrypt(data) { let key = CryptoJS.enc.Utf8.parse(SECRET_KEY); if (typeof data === "object") { data = JSON.stringify(data); } let plainText = CryptoJS.enc.Utf8.parse(data); let secretText = CryptoJS.AES.encrypt(plainText, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString(); return secretText; } /** * 解密數(shù)據(jù) * @param data 待解密數(shù)據(jù) */ function decrypt(data) { let key = CryptoJS.enc.Utf8.parse(SECRET_KEY); let result = CryptoJS.AES.decrypt(data, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString(CryptoJS.enc.Utf8); return JSON.parse(result); } </script> <!-- http請(qǐng)求模塊 --> <script type="text/javascript"> // 獲取加密數(shù)據(jù)并解密 axios.get("http://127.0.0.1:8080/user/getUser").then((res) => { console.log("接收到api返回加密數(shù)據(jù):"); console.log(decrypt(res.data.encryptData)); }); // 提交加密參數(shù) const data = { name: "Tom", age: "18", }; axios .post("http://127.0.0.1:8080/user/addUser", { encryptData: encrypt(data), }) .then((res) => { console.log("接收到api返回未加密數(shù)據(jù):"); console.log(res.data); }); </script> </body> </html>
2.4、接口測(cè)試
- 開(kāi)發(fā)環(huán)境不加密更易于開(kāi)發(fā)調(diào)試
- 生產(chǎn)環(huán)境需要數(shù)據(jù)加密
前后端都可以通過(guò)參數(shù) encryptData
判斷對(duì)方提交/返回的數(shù)據(jù)是否為加密數(shù)據(jù),如果是加密數(shù)據(jù)則進(jìn)行解密操作
接口返回加密數(shù)據(jù)
GET http://127.0.0.1:8080/user/getUser
未加密的數(shù)據(jù)
{ "message": "success", "data": { "name": "Tom", "age": "18" }, "code": 0 }
返回?cái)?shù)據(jù)
{ "message": "success", "encryptData": "kun2Wvk2LNKICaXIeIExA7jKRyqOV0qCv5KQXFOzfpQ=", "code": 0 }
客戶端提交參數(shù)
POST http://127.0.0.1:8080/user/addUser
提交數(shù)據(jù)
{ "data": { "name": "Tom", "age": "18" } }
提交加密數(shù)據(jù)
{ "encryptData": "kun2Wvk2LNKICaXIeIExA7jKRyqOV0qCv5KQXFOzfpQ=" }
2.5、總結(jié)
服務(wù)端
- 全局開(kāi)關(guān):通過(guò)控制
secret.enabled=true
全局開(kāi)啟返回?cái)?shù)據(jù)加密 - 全局局部:可以通過(guò)
SecretData
或者自定義PassSecretData
來(lái)控制單個(gè)控制器或者單個(gè)接口的需要或不需要加密
客戶端
- 可以根據(jù)開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境來(lái)控制是否開(kāi)啟加密
- 需要注意,F(xiàn)ormData傳輸文件的數(shù)據(jù)格式可以考慮不加密
相同點(diǎn)
- 服務(wù)端和客戶端都通過(guò)對(duì)方傳輸?shù)?code>encryptData來(lái)判斷是否為加密數(shù)據(jù)
- 服務(wù)端和客戶端都可以根據(jù)自己的環(huán)境來(lái)決定是否開(kāi)啟數(shù)據(jù)加密
完整代碼:spring-boot-demo/SpringBoot-Secret at master · mouday/spring-boot-demo · GitHub
以上就是SpringBoot使用AES對(duì)JSON數(shù)據(jù)加密和解密的實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot AES加解密的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- springboot使用國(guó)產(chǎn)加密算法方式,sm2和sm3加解密demo
- Springboot接口返回參數(shù)及入?yún)SA加密解密的過(guò)程詳解
- SpringBoot實(shí)現(xiàn)接口參數(shù)加密解密的示例代碼
- 關(guān)于Springboot數(shù)據(jù)庫(kù)配置文件明文密碼加密解密的問(wèn)題
- springboot實(shí)現(xiàn)敏感字段加密存儲(chǔ)解密顯示功能
- springboot實(shí)現(xiàn)注冊(cè)加密與登錄解密功能(demo)
- SpringBoot接口加密解密統(tǒng)一處理
- 在SpringBoot中通過(guò)jasypt進(jìn)行加密解密的方法
- Springboot實(shí)現(xiàn)密碼的加密解密
- SpringBoot實(shí)現(xiàn)國(guó)密SM4加密解密的使用示例
相關(guān)文章
SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存管理功能
本篇文章主要介紹spring boot緩存管理機(jī)制及相關(guān)概念,以及如何結(jié)合Redis實(shí)現(xiàn)緩存管理,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-01-01詳解spring-cloud與netflixEureka整合(注冊(cè)中心)
這篇文章主要介紹了詳解spring-cloud與netflixEureka整合(注冊(cè)中心),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02利用Java實(shí)現(xiàn)圖片轉(zhuǎn)化為ASCII圖的示例代碼
本文將詳細(xì)講解如何利用 Java 實(shí)現(xiàn)圖片轉(zhuǎn)化為 ASCII 圖,從項(xiàng)目背景與意義、相關(guān)技術(shù)知識(shí),到系統(tǒng)需求與架構(gòu)設(shè)計(jì),再到詳細(xì)實(shí)現(xiàn)思路、完整代碼和代碼解讀,最后對(duì)項(xiàng)目進(jìn)行總結(jié)與展望,需要的朋友可以參考下2025-03-03mybatis創(chuàng)建一個(gè)或多個(gè)新用戶 insert 字段和表名不確定時(shí)動(dòng)態(tài)添加問(wèn)題
這篇文章主要介紹了mybatis創(chuàng)建一個(gè)或多個(gè)新用戶 insert 字段和表名不確定時(shí)動(dòng)態(tài)添加問(wèn)題,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02springSecurity用戶認(rèn)證和授權(quán)的實(shí)現(xiàn)
Spring?Security?是一個(gè)開(kāi)源的安全框架,提供了基于權(quán)限的訪問(wèn)控制、身份認(rèn)證的功能,本文主要介紹了springSecurity用戶認(rèn)證和授權(quán),具有一定參考價(jià)值,感興趣的可以了解一下2024-04-04使用Jackson-json解析一個(gè)嵌套的json字符串
這篇文章主要介紹了使用Jackson-json解析一個(gè)嵌套的json字符串,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09