客户端和服务端都可以加密和解密,使用base64进行网络传输
加密方
字符串 -> AES加密 -> base64
解密方
base64 -> AES解密 -> 字符串
$ 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
pom.xml
4.0.0 org.springframework.boot spring-boot-starter-parent 2.7.7 com.example demo 0.0.1-SNAPSHOT demo Demo project for Spring Boot 1.8 3.5.2 org.springframework.boot spring-boot-starter-web commons-codec commons-codec 1.15 org.springframework.boot spring-boot-starter-thymeleaf org.projectlombok lombok true org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test junit junit test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
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 { // 设置静态资源映射 @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; /** * 处理跨域问题 */ @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; /** * 统一的请求体数据 */ @Data public class JsonRequest { /** * 未加密数据 */ private Object data; /** * 加密数据 */ private String encryptData; }
JsonResult.java
package com.example.demo.response; import lombok.Data; /** * 统一的返回体数据 不加密 */ @Data public class JsonResult{ private String message; private T data; private Integer code; public static JsonResult success(T data){ JsonResult jsonResult = new JsonResult<>(); jsonResult.setCode(0); jsonResult.setData(data); jsonResult.setMessage("success"); return jsonResult; } }
JsonResultVO.java
package com.example.demo.response; import lombok.Data; /** * 统一的返回体数据 加密 */ @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; /** * 对该Controller中的所有方法进行加解密处理 */ @RestController @SecretData public class UserController { @GetMapping("/user/getUser") public JsonResult getUser() { Mapuser = new HashMap<>(); user.put("name", "Tom"); user.put("age", "18"); return JsonResult.success(user); } @PostMapping("/user/addUser") public Object addUser(@RequestBody Map data) { System.out.println(data); return data; } }
application.yml
secret: key: 1234567890123456 # 密钥位数为16位 enabled: true # 开启加解密功能
SecretData.java
package com.example.demo.annotation; import org.springframework.web.bind.annotation.Mapping; import java.lang.annotation.*; /** * 只有添加有该注解的Controller类或是具体接口方法才进行数据的加密解密 */ @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; /** * 对请求内容进行解密 * 只有开启了加解密功能才会生效 * 仅对使用了@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); // 默认取data数据,如果提交加密数据则解密 String decryptData = null; if (jsonRequest.getEncryptData() != null) { decryptData = secretDataService.decrypt(jsonRequest.getEncryptData()); } else{ decryptData = objectMapper.writeValueAsString(jsonRequest.getData()); } String data = decryptData; // 解密后的数据 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; /** * 对响应内容加密 */ @ControllerAdvice @ConditionalOnProperty(name = "secret.enabled", havingValue = "true") public class EncryptResponseBodyAdvice implements ResponseBodyAdvice
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 { /** * 数据加密 * * @param data 待加密数据 * @return String 加密结果 */ String encrypt(String data); /** * 数据解密 * * @param data 待解密数据 * @return String 解密后的数据 */ 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; /** * 具体的加解密实现 */ @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; /** * 数据加密解密工具类 * 加密后返回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); } } }
浏览器中实现加密解密
templates/index.html
接口数据加密解密 查看控制台
前后端都可以通过参数 encryptData 判断对方提交/返回的数据是否为加密数据,如果是加密数据则进行解密操作
接口返回加密数据
GET http://127.0.0.1:8080/user/getUser
未加密的数据
{ "message": "success", "data": { "name": "Tom", "age": "18" }, "code": 0 }
返回数据
{ "message": "success", "encryptData": "kun2Wvk2LNKICaXIeIExA7jKRyqOV0qCv5KQXFOzfpQ=", "code": 0 }
客户端提交参数
POST http://127.0.0.1:8080/user/addUser
提交数据
{ "data": { "name": "Tom", "age": "18" } }
提交加密数据
{ "encryptData": "kun2Wvk2LNKICaXIeIExA7jKRyqOV0qCv5KQXFOzfpQ=" }
服务端
客户端
相同点
完整代码:https://github.com/mouday/spring-boot-demo/tree/master/SpringBoot-Secret
参考文章
上一篇:SQL优化15个技巧