SpringBoot⾃定义注解+实现
在实际⽣产项⽬中,经常需要对如⾝份证信息、⼿机号、真实姓名等的敏感数据进⾏加密数据库存储,但在业务代码中对敏感信息进⾏⼿动加解密则⼗分不优雅,甚⾄会存在错加密、漏加密、业务⼈员需要知道实际的加密规则等的情况。
本⽂将介绍使⽤springboot+mybatis+⾃定义注解的形式对敏感数据进⾏存储前拦截加密的详细过程。
⼀、什么是Mybatis Plugin
在mybatis官⽅⽂档中,对于Mybatis plugin的的介绍是这样的:
MyBatis 允许你在已映射语句执⾏过程中的某⼀点进⾏拦截调⽤。默认情况下,MyBatis 允许使⽤插件来拦截的⽅法调⽤包括:
//语句执⾏拦截
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// 参数获取、设置时进⾏拦截
ParameterHandler (getParameterObject, setParameters)
// 对返回结果进⾏拦截
ResultSetHandler (handleResultSets, handleOutputParameters)
//sql语句拦截
StatementHandler (prepare, parameterize, batch, update, query)
简⽽⾔之,即在执⾏sql的整个周期中,我们可以任意切⼊到某⼀点对sql的参数、sql执⾏结果集、sql语句本⾝等进⾏切⾯处理。基于这个特性,我们便可以使⽤其对我们需要进⾏加密的数据进⾏切⾯统⼀加密处理了(分页插件 pageHelper 就是这样实现数据库分页查询的)。
⼆、实现基于注解的敏感信息加解密
2.1 实现思路
对于数据的加密与解密,应当存在两个对数据进⾏拦截操作
参照官⽅⽂档,因此此处我们应当使⽤ParameterHandler对⼊参进⾏加密
使⽤ResultSetHandler对出参进⾏解密操作。
⽬标需要加密、解密的字段可能需要灵活变更,此时我们定义⼀个注解,对需要加密的字段进⾏注解,那么便可以配合对需要的数据进⾏加密与解密操作了。
mybatis的interceptor接⼝有以下⽅法需要实现。
public interface Interceptor {
//主要参数拦截⽅法
Object intercept(Invocation invocation) throws Throwable;
//mybatis插件链
default Object plugin(Object target) {return Plugin.wrap(target, this);}
//⾃定义插件配置⽂件⽅法
default void setProperties(Properties properties) {}
}
2.2 定义需要加密解密的敏感信息注解
定义注解敏感信息类(如实体类POJO\PO)的注解
/**
* 注解敏感信息类的注解
*/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}
定义注解敏感信息类中敏感字段的注解
/**
* 注解敏感信息类中敏感字段的注解
*/
@Inherited
@Target({ ElementType.Field })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
}
2.3 定义加密接⼝及其实现类
定义加密接⼝,⽅便以后拓展加密⽅法(如AES加密算法拓展⽀持PBE算法,只需要注⼊时指定⼀下便可)
public interface EncryptUtil {
/**
* 加密
*
* @param declaredFields paramsObject所声明的字段
* @param paramsObject  mapper中paramsType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
<T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}
EncryptUtil 的AES加密实现类,此处AESUtil为⾃封装的AES加密⼯具,需要的⼩伙伴可以⾃⾏封装,本⽂不提供。
@Component
public class AESEncrypt implements EncryptUtil {
@Autowired
AESUtil aesUtil;
/**
* 加密
*
* @param declaredFields paramsObject所声明的字段
* @param paramsObject  mapper中paramsType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
@Override
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = Annotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = (paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//加密这⾥我使⽤⾃定义的AES加密⼯具
field.set(paramsObject, pt(value));
}
}
}
return paramsObject;
}
}
2.4 实现⼊参加密
Myabtis包中的org.apache.ibatis.plugin.Interceptor接⼝要求我们实现以下三个⽅法
public interface Interceptor {
//核⼼拦截逻辑
Object intercept(Invocation invocation) throws Throwable;
//链
default Object plugin(Object target) {return Plugin.wrap(target, this);}
//⾃定义配置⽂件操作
default void setProperties(Properties properties) { }
}
因此,参考官⽅⽂档的⽰例,我们⾃定义⼀个⼊参加密。
@Intercepts 注解开启,@Signature 注解定义的实际类型。
@Signature中
type 属性指定当前使⽤StatementHandler 、ResultSetHandler、ParameterHandler,Executor的⼀种method 属性指定使⽤以上四种类型的具体⽅法(可进⼊class内部查看其⽅法)。
args 属性指定预编译语句
此处我们使⽤了 ParameterHandler.setParamters()⽅法,拦截l中paramsType的实例(即在每个含有paramsType属性mapper语句中,都执⾏该,对paramsType的实例进⾏拦截处理)
/**
* 加密
* 注意@Component注解⼀定要加上
*
* @author : tanzj
* @date : 2020/1/19.
*/
@Slf4j
@Component
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {
private final EncryptDecryptUtil encryptUtil;
@Autowired
public EncryptInterceptor(EncryptDecryptUtil encryptUtil) {
}
@Override
@Override
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这⾥的 Target() 便是parameterHandler
//若指定ResultSetHandler ,这⾥则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) Target();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = Class().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例
Object parameterObject = (parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = Class();
//校验该实例的类是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Null(sensitiveData)) {
//取出当前当前类所有字段,传⼊加密⽅法
Field[] declaredFields = DeclaredFields();
}
}
return invocation.proceed();
}
/**
* 切记配置,否则当前不会加⼊链
*/
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
//⾃定义配置写⼊,没有⾃定义配置的可以直接置空此⽅法
@Override
public void setProperties(Properties properties) {
}
}
⾄此完成⾃定义加密拦截加密。
2.5 定义解密接⼝及其实现类
解密接⼝,其中result为l中resultType的实例。
public interface DecryptUtil {
/
**
* 解密
*
* @param result resultType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
<T> T decrypt(T result) throws IllegalAccessException;
}
解密接⼝AES⼯具解密实现类
public class AESDecrypt implements DecryptUtil {
@Autowired
springboot推荐算法AESUtil aesUtil;
/**
* 解密
*
* @param result resultType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
@Override
public <T> T decrypt(T result) throws IllegalAccessException {
/
/取出resultType的类
Class<?> resultClass = Class();
Field[] declaredFields = DeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = Annotation(SensitiveField.class);            if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = (result);
//只⽀持String的解密
if (object instanceof String) {
String value = (String) object;
//对注解的字段进⾏逐⼀解密
field.set(result, aesUtil.decrypt(value));
}
}
}
return result;
}
}
2.6 定义出参解密

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。