SpringBoot接口加密解密统一处理

 更新时间:2019年08月15日 08:33:32   作者:恒奇恒毅  
这篇文章主要为大家详细介绍了SpringBoot接口加密解密统一处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用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");
  }
}

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • spring cloud gateway全局过滤器实现向request header中放数据

    spring cloud gateway全局过滤器实现向request header中放数据

    这篇文章主要介绍了spring cloud gateway全局过滤器实现向request header中放数据的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 使用java实现网络爬虫

    使用java实现网络爬虫

    这篇文章主要介绍了使用java实现网络爬虫,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • IDEA 2021.3 使用及idea2021.3.1激活使用方法

    IDEA 2021.3 使用及idea2021.3.1激活使用方法

    IDEA 全称 IntelliJ IDEA,是java语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具之一,今天通过本文给大家介绍idea2021.3.1激活及使用教程,感兴趣的朋友一起看看吧
    2022-01-01
  • Java线程状态转换关系实例解析

    Java线程状态转换关系实例解析

    这篇文章主要介绍了Java线程状态转换关系实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java使用JSONPath解析JSON完整内容详解

    Java使用JSONPath解析JSON完整内容详解

    这篇文章主要介绍了Java使用JSONPath解析JSON完整内容详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • Java多线程的实现方式比较(两种方式比较)

    Java多线程的实现方式比较(两种方式比较)

    Java多线程实现方式有两种,第一种是继承Thread类,第二种是实现Runnable接口,两种有很多差异,下面跟着本文一起学习吧
    2015-11-11
  • Java元素排序Comparable与Comparator的区别

    Java元素排序Comparable与Comparator的区别

    这篇文章主要介绍了Java元素排序Comparable与Comparator的区别,二者都是顶级的接口,但拥有的方法和用法是不同的,下面我们分别来看看具体是怎样的区别吧
    2022-05-05
  • JVM 体系结构详解

    JVM 体系结构详解

    本文主要介绍了JVM体系结构的相关知识。具有很好的参考价值。下面跟着小编一起来看下吧
    2017-03-03
  • 如何使用ByteArrayOutputStream下载文件

    如何使用ByteArrayOutputStream下载文件

    这篇文章主要介绍了如何使用ByteArrayOutputStream下载文件方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • MyBatis一对多关系映射方式小结

    MyBatis一对多关系映射方式小结

    在实际开发中经常会有一对多的表关系,因此掌握使用MyBatis处理一对多关系映射方式时很有必要的,本文就来介绍一下MyBatis一对多关系映射方式,感兴趣的可以了解一下
    2025-10-10

最新评论