SpringBoot接口加密解密统一处理
作者:恒奇恒毅 发布时间:2023-04-12 19:42:04
我们与客户端的接 * 互中,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密)、返回信息加密(服务端加密,客户端解密),但是也不是所有的接口都这样,有些接口可能不需要,我们可以使用注解来轻松达到此要求。
将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用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");
}
}
至此,一个完美的对接口的加密解密就实现了。
来源:https://blog.csdn.net/xxssyyyyssxx/article/details/88219298
猜你喜欢
- 1.BeanFactory1.1Spring提供了IOC容器的两种实现方式① BeanFactory:IOC容器的基本实现,是Spring内
- 微服务治理Spring Cloud 工具套件为微服务治理提供了全面的技术支持。这些治理工具主要包括服务的注册与发现、负载均衡管理、动态路由、
- 上篇文章中我们介绍了浅谈Spring的两种配置容器,接下来我们就了解下spring中的FactoryBean的相关内容,具体如下。从Sess
- 本文实例为大家分享了java实现简单石头剪刀布游戏的具体代码,供大家参考,具体内容如下问题描述Alice, Bob和Cindy一起玩猜拳的游
- 应用场景最近社区总有人发文章带上小广告,严重影响社区氛围,好气!对于这种类型的用户,就该永久拉黑!社区的安全框架使用了 spring-sec
- NO.1–注释在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容
- C++对string进行大小写转换操作方法方法一:使用C语言之前的方法,使用函数,进行转换#include <iostream>
- java 引用类型的数据传递的是内存地址java中引用类型的数据,传递的是内存地址,像类,数组,接口,String等等都是引用类型!看下面的
- 问题在项目过程中使用MyBatis-Puls的saveBatch一次性添加大量数据时很慢原因MyBatis-Puls的saveBatch默认
- 这篇文章主要介绍了springboot如何使用AOP做访问请求日志,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价
- 今天遇到pom中添加dependency时相关的jar会自动下载,但是左边的External Libraries中一直获取不到添加的jar问
- 阿里终面在线编程题,写出来与大家分享一下 有一个单向链表
- 在java程序开发中,ftp用的比较多,经常打交道,比如说向FTP服务器上传文件、下载文件,本文给大家介绍如何利用jakarta commo
- 前言Mybatis真正强大的地方在于SQL映射语句,这也是它的魅力所在。相对于它强大的功能,SQL映射文件的配置却非常简单,我上篇文章语句讲
- 1.用途在SpringBoot中,通过jasypt可以进行加密解密. 这个是双向的, 且可以配置密钥.2.使用:2.1通过UT创建工具类,并
- 本文实例为大家分享了Java读取并下载网络文件的具体代码,供大家参考,具体内容如下import java.io.ByteArrayOutpu
- 目录1、通过session中的token验证步骤1:创建自定义注解步骤2:创建自定义 * (@slf4j是lombok的注解)步骤3:将自定
- Pager.javapackage pers.kangxu.datautils.common;import java.io.Serializ
- java对象拷贝详解及实例Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的:@Testpublic
- Java中的main函数的详细介绍JAVA中的主函数是我们再熟悉不过的了,相信每个学习过JAVA语言的人都能够熟练地写出这个程序的入口函数,