Java - 接口加密&解密
需求 & 解决思路
需求:Spring Boot 项目中,对部分POST请求的参数 - body进行解密处理。对部分接口返回的值进行加密处理。
解决思路:
- 首先第一反应就是使用AOP,加密&解密 是边缘业务且是可复用的。
- 其次,加密 & 解密 操作都是针对接口而言的,那么SpringBoot内有提供了对接口的拦截类
代码实现
解密操作
首先,创建一个注解类:Decrypt
,用来标识哪些接口的参数需要解密。
@Documented
@Retention(RetentionPolicy.RUNTIME)//元注解,标识当前注解需要保留至JVM中
@Target({ElementType.METHOD, ElementType.PARAMETER})//表示该注解作用在方法和参数上
public @interface Decrypt {
}
创建DecryptRequest
类,真正实现解密操作。
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
//这里使用的Hutool工具包内提供的RSA加密算法,RSA_PRIVATE_KEY 这个参数需要自己生成
private static final RSA rsa_de = new RSA(RSA_PRIVATE_KEY, null);
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//首先此处判断该接口上是否存在@Decrypt注解,不存在则不需要处理
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 {
String s = new String(body);
byte[] decrypt = rsa_de.decrypt(s, KeyType.PrivateKey);
return new HttpInputMessage() {
@Override
public InputStream getBody(){
return new ByteArrayInputStream(decrypt);
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
} catch (Exception e) {
e.printStackTrace();
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
-
@ControllerAdvice
:该注解主要是针对控制器进行拦截操作 - 即就是AOP。具体的拦截是需要在注解类内部的方法进行实现。通常有以下用法:@ExceptionHandler
:用来定义全局异常处理类@ModelAttribute
:用来预设全局的数据@InitBinder
:实现对请求参数的预处理
-
RequestBodyAdvice
:它是Spring提供的一个全局解决方案,用来解决对请求参数做的一些统一的操作,如:参数过滤、字符编码、加解密等。它仅对使用了 @RequestBody 注解的控制器生效生效。它是一个顶级接口,具体功能由子类实现。-
RequestBodyAdviceAdapter
:RequestBodyAdvice 的抽象实现类,通常我们只需要集成这个类按需实现。public abstract class RequestBodyAdviceAdapter implements RequestBodyAdvice { /** * 默认实现返回传入的输入消息 */ @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { return inputMessage; } /** * 默认实现返回传入的正文 */ @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } /** * 默认实现返回传入的正文。 */ @Override @Nullable public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } }
具体使用
@RestController @RequestMapping("/api/test") public class TestController { @PostMapping("decrypt") public String decrypt(@RequestBody @Decrypt UserVo vo) { return vo.toString(); } }
-
接口返回值加密
首先项目中需要统一返回值格式
@Data //这里使用了lombok 插件
@Accessors(chain = true)
@AllArgsConstructor
public class ResultData implements Serializable {
private static final long serialVersionUID = 8545996863226528798L;
private Integer code = 200;
private String msg = "success";
private Object data;
private ResultData(Object data) {
this.data = data;
}
public static ResultData ok() {
return new ResultData(null);
}
public static ResultData ok(Object data) {
return new ResultData(data);
}
public static ResultData error(ErrorResultEnum resultEnum) {
return new ResultData(resultEnum.getCode(), resultEnum.getMsg(), null);
}
public static ResultData error(Integer code, String msg) {
return new ResultData(code, msg, null);
}
}
之后,返回参数的加密是针对ResultData中的data属性而言的。
第一步:创建一个注解:@Encrypt
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Encrypt {
}
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<Object> {
private static final RSA rsa_de = new RSA(RSA_PRIVATE_KEY, null);
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(Encrypt.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ResultData resultData = (ResultData) body;
Object data = resultData.getData();
ObjectMapper ob = new ObjectMapper();
if (data!= null){
String s = null;
try {
s = rsa_de.encryptBase64(ob.writeValueAsString(data), KeyType.PrivateKey);
resultData.setData(s);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return resultData;
}
}