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");
  }
}

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

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

(0)

相关推荐

  • java servlet手机app访问接口(一)数据加密传输验证

    前面几篇关于servlet的随笔,算是梳理了servlet的简单使用流程,接下去的文章将主要围绕手机APP访问接口这块出发续写,md5加密传输--->短信验证--->手机推送--->分享--->百度云图---->支付....第三方的业务 ...由于我是新手我也是一边学一边写,不足地方希望谅解. 今天这篇文章主要涉及到 javaservlet传输数据的加密,客户端请求参数的组合,并且会附带上我中途遇到的所有问题以及解决方法. 由于手机访问接口是公布出来的,所以不管用什么语言编写

  • SpringBoot接口加密解密统一处理

    我们与客户端的接口交互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密).返回信息加密(服务端加密,客户端解密),但是也不是所有的接口都这样,有些接口可能不需要,我们可以使用注解来轻松达到此要求. 将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用Controller的ControllerAdvice来拦截所有的请求,在其中判断是否需要加密解密,即可达到要求. 使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在

  • SpringBoot接口数据加解密实战记录

    这日,刚撸完2行代码,正准备掏出手机摸鱼放松放松,只见老大朝我走过来,并露出一个”善意“的微笑,兴伟呀,xx项目有于安全问题,需要对接口整体进行加密处理,你这方面比较有经验,就给你安排上了哈,看这周内提测行不...,额,摸摸头上飘摇着而稀疏的长发,感觉我爱了. 和产品.前端同学对外需求后,梳理了相关技术方案, 主要的需求点如下: 尽量少改动,不影响之前的业务逻辑: 考虑到时间紧迫性,可采用对称性加密方式,服务需要对接安卓.IOS.H5三端,另外考虑到H5端存储密钥安全性相对来说会低一些,故分针对

  • 关于springboot的接口返回值统一标准格式

    目录 一.目标 二.为什么要对springboot的接口返回值统一标准格式? 第一种格式:response为String 第二种格式:response为Objct 第三种格式:response为void 第四种格式:response为异常 三.定义response的标准格式 四.初级程序员对response代码封装 步骤1:把标准格式转换为代码 步骤2:把状态码存在枚举类里面 步骤3:加一个体验类 五.高级程序员对response代码封装 步骤1:采用ResponseBodyAdvice技术来实

  • PHP的RSA加密解密方法以及开发接口使用

    网络安全问题很重要,尤其是保证数据安全,遇到很多在写接口的程序员直接都是明文数据传输,在我看来这是很不专业的.本人提倡经过接口的数据都要进行加密解密之后进行使用. 这篇文章主要介绍使用PHP开发接口,数据实现RSA加密解密后使用,实例分析了PHP自定义RSA类实现加密与解密的技巧,非常具有实用价值,需要的朋友可以参考下. 简单介绍RSA RSA加密算法是最常用的非对称加密算法,CFCA在证书服务中离不了它.但是有不少新手对它不太了解.下面仅作简要介绍.RSA是第一个比较完善的公开密钥算法,它既能

  • Springboot实现密码的加密解密

    现今对于大多数公司来说,信息安全工作尤为重要,就像京东,阿里巴巴这样的大公司来说,信息安全是最为重要的一个话题,举个简单的例子: 就像这样的密码公开化,很容易造成一定的信息的泄露.所以今天我们要讲的就是如何来实现密码的加密和解密来提高数据的安全性. 在这首先要引入springboot融合mybatis的知识,如果有这方面不懂得同学,就要首先看一看这方面的知识: 推荐大家一个比较好的博客: 程序猿DD-翟永超 http://blog.didispace.com/springbootmybatis/

  • 在SpringBoot中通过jasypt进行加密解密的方法

    1.用途 在SpringBoot中,通过jasypt可以进行加密解密. 这个是双向的, 且可以配置密钥. 2.使用: 2.1通过UT创建工具类,并认识jasypt import org.jasypt.util.text.BasicTextEncryptor; import org.junit.Test; public class UtilTests { @Test public void jasyptTest() { BasicTextEncryptor encryptor = new Basi

  • Spring Boot 接口参数加密解密的实现方法

    因为有小伙伴刚好问到这个问题,松哥就抽空撸一篇文章和大家聊聊这个话题. 加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据.不过 SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便. 所以今天这篇文章有两个目的: 分享参数/响应加解密的思路. 分享 Response

  • jmeter接口测试之使用rsa算法加密解密的代码

    本篇介绍jmeter 使用rsa算法进行加密参数 如果测试过程中,部分接口采用了rsa加密算法,我们的jmeter 也是可以直接拿来调用的,不需要开发配合去掉加密代码! 直接上代码 import org.apache.commons.codec.binary.Base64; import java.io.ByteArrayOutputStream; import java.security.Key; import java.security.KeyFactory; import java.sec

  • 关于Springboot数据库配置文件明文密码加密解密的问题

    有时候因为安全问题,需要把配置文件的中数据库用户名密码由明文改成密文,大多数其实是为了应付甲方而已. 1.pom.xml引入依赖 <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>2.1.0</version> </dependenc

  • SpringBoot接口如何统一异常处理

    目录 为什么要优雅的处理异常 实现案例 @ControllerAdvice异常统一处理 Controller接口 运行测试 进一步理解 @ControllerAdvice还可以怎么用? @ControllerAdvice是如何起作用的(原理)? 为什么要优雅的处理异常 如果我们不统一的处理异常,经常会在controller层有大量的异常处理的代码, 比如: @Slf4j @Api(value = "User Interfaces", tags = "User Interfac

随机推荐