SpringBoot接口加密解密统一处理

作者:恒奇恒毅 时间:2023-04-12 19:42:04 

我们与客户端的接 * 互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密)、返回信息加密(服务端加密,客户端解密),但是也不是所有的接口都这样,有些接口可能不需要,我们可以使用注解来轻松达到此要求。

将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用Controller的ControllerAdvice来拦截所有的请求,在其中判断是否需要加密解密,即可达到要求。

使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在Controller的类和方法上,其中一个为false就不执行了。像这样:


@RestController
@RequestMapping("/test")
//@DecryptRequest
@EncryptResponse
public class TestController {
 @Autowired
 @Qualifier("rrCrypto")
 private Crypto crypto;

@DecryptRequest(false)
 @EncryptResponse(false)
 @RequestMapping(value = "/enc" , method = RequestMethod.POST)
 public String enc(@RequestBody String body){
   return crypto.encrypt(body);
 }
}

定义参数解密的注解,DecryptRequest。


/**
* 解密注解
*
* <p>加了此注解的接口(true)将进行数据解密操作(post的body) 可
*  以放在类上,可以放在方法上 </p>
* @author xiongshiyan
*/
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequest {
 /**
  * 是否对body进行解密
  */
 boolean value() default true;
}

定义返回信息加密的注解,EncryptResponse。


/**
* 加密注解
*
* <p>加了此注解的接口(true)将进行数据加密操作
*  可以放在类上,可以放在方法上 </p>
* @author 熊诗言
*/
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponse {
 /**
  * 是否对结果加密
  */
 boolean value() default true;
}

这两个注解可以放在类和方法上,遵循一样的逻辑,即:类上的注解 && 方法上的注解,一方没有即为true,都为false为false。逻辑主要在 NeedCrypto 中。


/**
* 判断是否需要加解密
* @author xiongshiyan at 2018/8/30 , contact me with email yanshixiong@126.com or phone 15208384257
*/
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;
 }
}

然后定义ControllerAdvice,对于请求解密的,定义 DecryptRequestBodyAdvice ,实现 RequestBodyAdvice 。


/**
* 请求数据接收处理类<br>
*
* 对加了@Decrypt的方法的数据进行解密操作<br>
*
* 只对 @RequestBody 参数有效
* @author xiongshiyan
*/
@ControllerAdvice
@ConditionalOnProperty(prefix = "spring.crypto.request.decrypt", name = "enabled" , havingValue = "true", matchIfMissing = true)
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

@Value("${spring.crypto.request.decrypt.charset:UTF-8}")
 private String charset = "UTF-8";

@Autowired
 @Qualifier("rrCrypto")
 private Crypto crypto;

@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
 Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}

@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 , charset , crypto);
}
return inputMessage;
}

@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
 Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}

标上注解 ConditionalOnProperty 表示只有条件为true的时候才开启解密功能,一个配置即可打开或者关闭解密功能。真正的解密逻辑留给 DecryptHttpInputMessage , 它又委托给 Crypto。


/**
*
* @author xiongshiyan
*/
public class DecryptHttpInputMessage implements HttpInputMessage {
 private HttpInputMessage inputMessage;
 private String charset;
 private Crypto crypto;

public DecryptHttpInputMessage(HttpInputMessage inputMessage, String charset , Crypto crypto) {
   this.inputMessage = inputMessage;
   this.charset = charset;
   this.crypto = crypto;
 }

@Override
 public InputStream getBody() throws IOException {
   String content = IoUtil.read(inputMessage.getBody() , charset);

String decryptBody = crypto.decrypt(content, charset);

return new ByteArrayInputStream(decryptBody.getBytes(charset));
 }

@Override
 public HttpHeaders getHeaders() {
   return inputMessage.getHeaders();
 }
}

对于返回值加密,定义 EncryptResponseBodyAdvice,实现 ResponseBodyAdvice。


/**
* 请求响应处理类<br>
*
* 对加了@Encrypt的方法的数据进行加密操作
*
* @author 熊诗言
*
*/
@ControllerAdvice
@ConditionalOnProperty(prefix = "spring.crypto.response.encrypt", name = "enabled" , havingValue = "true", matchIfMissing = true)
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {

@Value("${spring.crypto.request.decrypt.charset:UTF-8}")
 private String charset = "UTF-8";

@Autowired
 @Qualifier("rrCrypto")
 private Crypto crypto;

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}

@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(crypto.encrypt(xx, charset));

return responseMsg;
}

}

真正的加密逻辑委托给 Crypto ,这是一个加密解密的接口,有很多实现类,参见:链接


/**
* Request-Response加解密体系的加解密方式
* @author xiongshiyan at 2018/8/14 , contact me with email yanshixiong@126.com or phone 15208384257
*/
@Configuration
public class RRCryptoConfig {
 /**
  * 加密解密方式使用一样的
  */
 @Bean("rrCrypto")
 public Crypto rrCrypto(){
   return new AesCrypto("密钥key");
 }
}

至此,一个完美的对接口的加密解密就实现了。

来源:https://blog.csdn.net/xxssyyyyssxx/article/details/88219298

标签:SpringBoot,接口,加密解密
0
投稿

猜你喜欢

  • C#实现图片上传(PC端和APP)保存及 跨域上传说明

    2022-11-10 17:05:11
  • Android RecyclerView实现水平、垂直方向分割线

    2023-07-24 15:40:35
  • Android编程之DatePicker和TimePicke简单时间监听用法分析

    2022-07-04 00:42:24
  • C#记录消息到日志文件的方法

    2021-09-17 23:10:39
  • SpringBoot上传临时文件被删除引起报错的解决

    2022-05-28 23:46:24
  • c#删除代码中的单行注释行示例

    2023-05-11 22:44:49
  • SpringBoot使用Spark过程详解

    2021-07-30 06:19:21
  • 基于Android LayoutInflater的使用介绍

    2023-12-22 23:50:03
  • Java状态设计模式实现对象状态转换的优雅方式

    2023-11-20 10:33:59
  • DevExpress实现为TextEdit设置水印文字的方法

    2021-11-11 14:27:32
  • SpringBoot全局异常处理与定制404页面的方法

    2021-08-07 18:54:56
  • Android获取内外置存储卡的方法

    2023-12-03 00:41:09
  • Android自定义视图中图片的处理

    2023-06-29 12:07:27
  • 在Java中判断两个Long对象是否相等

    2022-09-01 11:22:10
  • C#正则表达式大全

    2023-12-04 13:06:37
  • C#访问及调用类中私有成员与方法示例代码

    2022-06-02 05:30:35
  • Android设置theme中可能遇到的坑

    2021-08-05 06:20:26
  • 新浪微博第三方登录界面上下拉伸图片之第三方开源PullToZoomListViewEx(二)

    2021-07-25 04:52:35
  • Jackson 反序列化时实现大小写不敏感设置

    2021-11-18 06:17:18
  • Gradle快速安装及入门

    2021-11-25 09:33:28
  • asp之家 软件编程 m.aspxhome.com