客户端和服务端都可以加密和解密,使用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() {
Map user = 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个技巧