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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xxssyyyyssxx/article/details/88219298
项目参见 https://gitee.com/xxssyyyyssxx/affect-inoutput

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

将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用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 [email protected] 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 ,这是一个加密解密的接口,有很多实现类,参见 https://gitee.com/xxssyyyyssxx/common-crypto :

/**
 * Request-Response加解密体系的加解密方式
 * @author xiongshiyan at 2018/8/14 , contact me with email [email protected] or phone 15208384257
 */
@Configuration
public class RRCryptoConfig {
    /**
     * 加密解密方式使用一样的
     */
    @Bean("rrCrypto")
    public Crypto rrCrypto(){
        return new AesCrypto("密钥key");
    }
}
至此,一个完美的对接口的加密解密就实现了。
————————————————
版权声明:本文为CSDN博主「恒奇恒毅」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xxssyyyyssxx/article/details/88219298

发布了51 篇原创文章 · 获赞 80 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/xiyang_1990/article/details/103061419