加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便。
1、首先自定义加密与解密注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义加密注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义解密注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Decrypt {
}
2、其次,定义一个加密工具类,加密这块有多种方案可以选择,对称加密、非对称加密,其中对称加密又可以使用 AES、DES、3DES 等不同算法,这里我们使用 Java 自带的 Cipher 来实现AES对称加密算法
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
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 String decrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
}
/**
* 方便测试(正常情况下是前端加密请求参数,后端接收到请求参数进行解密操作,执行完逻辑之后,对返回的数据进行加密返回给前端,前端解密获取数据)
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//String encryptKey = "1234567890123456";
String encryptKey = "1111111111111111";
String a = "{\"id\":\"1\",\"name\":\"tom\",\"age\":18}";
String encrypt = encrypt(a.getBytes(), encryptKey.getBytes());
System.out.println("【加密之后数据:】" + encrypt.replaceAll("\\r\\n", ""));
String b = "sVEcp4uhNCW3LdTeCEUJxDkjppxKuKItagNSRYO5jRzzDGnE+UXmVOvUs4ixyPeQ";
String decrypt = decrypt(b.getBytes(), encryptKey.getBytes());
System.out.println("【解密之后数据:】" + decrypt);
}
}
所有准备工作做完了,接下来就该正式加解密了。
在这里和大家分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,RequestBodyAdvice 在做解密的时候倒是没啥问题,而 ResponseBodyAdvice 在做加密的时候则会有一些局限,不过影响不大,如果想非常灵活的掌控一切,那还是自定义过滤器吧。
另外还有一点需要注意,ResponseBodyAdvice 在你使用了 @ResponseBody 注解的时候才会生效,RequestBodyAdvice 在你使用了 @RequestBody 注解的时候才会生效,换言之,前后端都是 JSON 交互的时候,这两个才有用。
先来看接口加密:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hihonor.behavior.analysis.pojo.vo.common.BaseResView;
import org.springframework.beans.factory.annotation.Value;
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;
/**
* 接口加密
*/
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<BaseResView> {
@Value("${spring.encrypt.key:1234567890123456}")
private String encryptKey;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(Encrypt.class);
}
@Override
public BaseResView beforeBodyWrite(BaseResView body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
System.out.println("=======================>>>>>>>>>>>>>>>>>>>>>:" + encryptKey);
try {
if (body.getDesc() != null) {
body.setDesc(AESUtils.encrypt(body.getDesc().getBytes(), encryptKey.getBytes()));
}
if (body.getData() != null) {
body.setData(AESUtils.encrypt(new ObjectMapper().writeValueAsBytes(body.getData()), encryptKey.getBytes()));
}
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
}
再是接口解密:
import org.springframework.beans.factory.annotation.Value;
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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* 接口解密
*/
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
@Value("${spring.encrypt.key:1234567890123456}")
private String encryptKey;
@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 {
System.out.println("=======================>>>>>>>>>>>>>>>>>>>>>:" + encryptKey);
byte[] body = new byte[inputMessage.getBody().available()];
inputMessage.getBody().read(body);
try {
String decrypt = AESUtils.decrypt(body, encryptKey.getBytes());
final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt.getBytes());
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);
}
}
如果想要修改加密密钥,可以在 application.properties 中添加如下配置:
spring:
encrypt:
key: 1111111111111111
@Value("${spring.encrypt.key:1234567890123456}")
private String encryptKey;
@Value注解是Spring框架中的一种注解,用于将配置文件中的值注入到Java类中的字段中。
在这个例子中,@Value注解用于将一个名为spring.encrypt.key的配置值注入到encryptKey字段中。如果spring.encrypt.key在配置文件中没有被定义,那么默认值1234567890123456将被使用
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@GetMapping("/test1")
@Encrypt
public Result getUser() {
User user = new User();
user.setId(1);
user.setName("tom");
user.setAge(18);
return Result.ok(user);
}
@PostMapping("/test2")
public Result addUser(@Decrypt @RequestBody User user) {
System.out.println("user = " + user);
return Result.ok(user);
}
}
这里的 Result 为封装好的统一结果集。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Result {
private Integer code;
private String desc;
private Object Data;
public static Result ok(String desc) {
return new Result(200, desc, null);
}
public static Result ok(String desc, Object obj) {
return new Result(200, desc, obj);
}
public static Result error(String desc) {
return new Result(500, desc, null);
}
public static Result error(String desc, Object obj) {
return new Result(500, desc, obj);
}
}
最后,正式开始我们的测试环节
test1:可以看到返回的出参已经加密
test2:可以看出请求体正常解密且返回数据