接口请求和响应加密

在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作.

我们与客户端的接口交互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密)、返回信息加密(服务端加密,客户端解密)

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

 

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Spring AOP实现对接口请求响应的参数加密。具体实现可以参考以下步骤: 1. 定义一个加密工具类,实现参数加密的逻辑。 2. 定义一个切面类,使用@Before和@AfterReturning注解分别在接口请求前和响应后进行参数加密和解密。 3. 在Spring Boot的配置文件中配置切面类。 4. 在接口方法上添加@Encrypt和@Decrypt注解,指定需要加密和解密的参数。 以下是一个简单的示例代码: 加密工具类: ``` public class EncryptUtils { public static String encrypt(String data) { // 实现加密逻辑 } public static String decrypt(String data) { // 实现解密逻辑 } } ``` 切面类: ``` @Aspect @Component public class EncryptAspect { @Before("@annotation(com.example.demo.annotation.Encrypt)") public void encryptRequest(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String encryptedData = EncryptUtils.encrypt((String) arg); // 替换原始参数 arg = encryptedData; } } } @AfterReturning(value = "@annotation(com.example.demo.annotation.Decrypt)", returning = "result") public void decryptResponse(Object result) { if (result instanceof String) { String decryptedData = EncryptUtils.decrypt((String) result); // 替换返回值 result = decryptedData; } } } ``` 配置文件: ``` @Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public EncryptAspect encryptAspect() { return new EncryptAspect(); } } ``` 接口方法: ``` @RestController public class UserController { @PostMapping("/user") @Encrypt @Decrypt public User createUser(@RequestBody User user) { // 处理业务逻辑 return user; } } ``` 以上代码仅供参考,实际实现需要根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值