首页 > 编程 > Java > 正文

SpringBoot接口加密解密统一处理

2019-11-26 08:43:07
字体:
来源:转载
供稿:网友

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

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

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

@RestController@RequestMapping("/test")//@DecryptRequest@EncryptResponsepublic 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)@Documentedpublic @interface DecryptRequest {  /**   * 是否对body进行解密   */  boolean value() default true;}

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

/** * 加密注解 * * <p>加了此注解的接口(true)将进行数据加密操作 *  可以放在类上,可以放在方法上 </p> * @author 熊诗言 */@Target({ElementType.METHOD , ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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 */@Configurationpublic class RRCryptoConfig {  /**   * 加密解密方式使用一样的   */  @Bean("rrCrypto")  public Crypto rrCrypto(){    return new AesCrypto("密钥key");  }}

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

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表