在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作.
我们与客户端的接口交互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密)、返回信息加密(服务端加密,客户端解密)
web端加密可通过js
前置知识:
基于RequestBodyAdvice和ResponseBodyAdvice来实现spring中参数的加密和解密:
RequestBodyAdvice:在 sping 4.2 新加入的一个接口,它可以使用在 @RequestBody 或 HttpEntity 修改的参数之前进行参数的处理,比如进行参数的解密。
ResponseBodyAdvice:在 spring 4.1 新加入的一个接口,在消息体被HttpMessageConverter写入之前允许Controller 中 @ResponseBody修饰的方法或ResponseEntity调整响应中的内容,比如进行相应的加密。
RequestBodyAdvice和ResponseBodyAdvice,需要适配器类上加上
@ControllerAdvice
注解,才能起作用;
@ControllerAdvice是组件注解,他使得其实现类能够被classpath扫描自动发现,如果应用是通过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。
注解@ControllerAdvice的类可以拥有@ExceptionHandler, @InitBinder或 @ModelAttribute注解的方法,并且这些方法会被应用到控制器类层次的所有@RequestMapping方法上。
@RestControllerAdvice 类似于 @RestController 与 @Controller的区别
1)注解有@ControllerAdvice的类, 需要在具体方法上同时添加@ExceptionHandler和@ResponseBody注解;
2)注解有@RestControllerAdvice的类,只需要在具体方法上添加@ExceptionHandler注解
@ControllerAdvice
public class BaseController {
public final String error = "ERROR";
/**
* 1.追加转换器
* 2.追加校验器
*
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
System.out.println("initBinder");
//统一日期处理
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
//追加校验器
binder.addValidators(personValidator);
}
/**
* 异常处理
*
* @return
*/
@ExceptionHandler({Exception.class})
@ResponseBody
public Object handException() {
return error;
}
// @ModelAttribute
}
功能实现:
将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用Controller的ControllerAdvice来拦截所有的请求,在其中判断是否需要加密解密,即可达到要求。
使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在Controller的类和方法上,其中一个为false就不执行了。
1,定义参数解密的注解,DecryptRequest和定义返回信息加密的注解,EncryptResponse
/**
* 解密注解
*
* 加了此注解的接口(true)将进行数据解密操作(post的body) 可
* 以放在类上,可以放在方法上
*/
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequest {
/**
* 是否对body进行解密
*/
boolean value() default true;
}
/**
* 加密注解
*
*加了此注解的接口(true)将进行数据加密操作
* 可以放在类上,可以放在方法上
*/
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponse {
/**
* 是否对结果加密
*/
boolean value() default true;
}
两个注解可以放在类和方法上,遵循一样的逻辑,即:类上的注解 && 方法上的注解,一方没有即为true,都为false为false:
/**
* 判断是否需要加解密
*/
class NeedCrypto {
private NeedCrypto(){}
/**
* 是否需要对结果加密
* 1.类上标注或者方法上标注,并且都为true
* 2.有一个标注为false就不需要加密
*/
static boolean needEncrypt(MethodParameter returnType) {
boolean encrypt = false;
boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class);
boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class);
if(classPresentAnno){
//类上标注的是否需要加密
encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value();
//类不加密,所有都不加密
if(!encrypt){
return false;
}
}
if(methodPresentAnno){
//方法上标注的是否需要加密
encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value();
}
return encrypt;
}
/**
* 是否需要参数解密
* 1.类上标注或者方法上标注,并且都为true
* 2.有一个标注为false就不需要解密
*/
static boolean needDecrypt(MethodParameter parameter) {
boolean encrypt = false;
boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);
boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class);
if(classPresentAnno){
//类上标注的是否需要解密
encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value();
//类不加密,所有都不加密
if(!encrypt){
return false;
}
}
if(methodPresentAnno){
//方法上标注的是否需要解密
encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value();
}
return encrypt;
}
}
2,编写RequestBodyAdvice接口实现类,实现数据的解密操作
supports
:判断当前参数是否需要使用自定义的适配器来处理;beforeBodyRead
:HTTP内容被转化为对象前执行的方法;afterBodyRead
:HTTP内容被消息转换器转换为对象后执行的方法;
/**
* 请求数据接收处理类<br>
*
* 对加了@Decrypt的方法的数据进行解密操作<br>
* 只对 @RequestBody 参数有效
*/
@ControllerAdvice
@ConditionalOnProperty(prefix = "spring.crypto.request.decrypt", name = "enabled" , havingValue = "true", matchIfMissing = true)
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
//return returnType.hasMethodAnnotation(ResponseBody.class);
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
if( NeedCrypto.needDecrypt(parameter) ){
return new DecryptHttpInputMessage(inputMessage, "UTF-8");
}
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
标上注解 ConditionalOnProperty 表示只有条件为true的时候才开启解密功能,一个配置即可打开或者关闭解密功能。真正的解密逻辑留给 DecryptHttpInputMessage :
/**
*
* @author xiongshiyan
*/
public class DecryptHttpInputMessage implements HttpInputMessage {
private HttpInputMessage inputMessage;
private String charset;
public DecryptHttpInputMessage(HttpInputMessage inputMessage, String charset) {
this.inputMessage = inputMessage;
this.charset = charset;
this.crypto = crypto;
}
@Override
public InputStream getBody() throws IOException {
String content = IoUtil.read(inputMessage.getBody() , charset);
String decryptBody = CryptPojoUtils.decryptField(content);
return new ByteArrayInputStream(decryptBody.getBytes(charset));
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
}
3,编写ResponseBodyAdvice接口实现类,实现数据的加密操作
/**
* 请求响应处理类<br>
* 对加了@Encrypt的方法的数据进行加密操作
*
*/
@ControllerAdvice
@ConditionalOnProperty(prefix = "spring.crypto.response.encrypt", name = "enabled" , havingValue = "true", matchIfMissing = true)
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
//return returnType.hasMethodAnnotation(ResponseBody.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
boolean encrypt = NeedCrypto.needEncrypt(returnType);
if( !encrypt ){
return body;
}
if(!(body instanceof ResponseMsg)){
return body;
}
//只针对ResponseMsg的data进行加密
ResponseMsg responseMsg = (ResponseMsg) body;
Object data = responseMsg.getData();
if(null == data){
return body;
}
String xx;
Class<?> dataClass = data.getClass();
if(dataClass.isPrimitive() || (data instanceof String)){
xx = String.valueOf(data);
}else {
//JavaBean、Map、List等先序列化
if(List.class.isAssignableFrom(dataClass)){
xx = JsonUtil.serializeList((List<Object>) data);
}else if(Map.class.isAssignableFrom(dataClass)){
xx = JsonUtil.serializeMap((Map<String, Object>) data);
}else {
xx = JsonUtil.serializeJavaBean(data);
}
}
//加密
responseMsg.setData(CryptPojoUtils.encryptField(xx));
return responseMsg;
}
}