快速实现
本章将按照上一章的设计思路进行一个快速实现。
快速构架
首先我们定义加解密器的接口:
public interface SafetyCipher {
Object encrypt(Object value);
Object decrypt(Object value);
}
这里我们先以Base64 为例,做一个实现并注册为Spring Bean:
@Component
public class Base64Cipher implements SafetyCipher {
private static final Base64.Encoder encoder = Base64.getEncoder();
private static final Base64.Decoder decoder = Base64.getDecoder();
public Object encrypt(Object value) {
String str = transToString(value);
return encoder.encodeToString(str.getBytes(StandardCharsets.UTF_8));
}
public Object decrypt(Object value) {
String str = transToString(value);
return new String(decoder.decode(str), StandardCharsets.UTF_8);
}
private String transToString(Object value) {
if (value instanceof String) {
return (String) value;
}
return String.valueOf(value);
}
}
然后在快速实现拦截器,加密拦截器如下:
/**
* 这里我们拦截select和update方法,将参数进行加密
*/
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class EncryptInterceptor implements Interceptor {
@Autowired
private SafetyCipher safetyCipher;
public Object intercept(Invocation invocation) throws Throwable {
Object param = invocation.getArgs()[1]; // 直接获取参数
safetyCipher.encrypt(param);
return invocation.proceed();
}
}
解密拦截器:
/**
* 相对加密拦截,解密就单纯很多,直接拦截返回结果进行解密即可
*/
@Component
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}))
public class DecryptInterceptor implements Interceptor {
@Autowired
private SafetyCipher safetyCipher;
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
List<Object> rows = (List<Object>) invocation.proceed();
safetyCipher.decrypt(rows);
return rows;
}
}
这里我们的基础实现就完成了,显然这离我们的目标还差很多,但我们是快速实现,先构建主体运行流程,之后在进行细节上的补充。
注解补充
我们期望的是一个通过注解来实现字段级的加解密配置,那么我们还应该定义一个作用于实体字段的注解,并且应该在加解密的时候根据注解进行加解密的控制,对没有进行注解标注的字段不进行处理,下面我们就来一步步完成。
首先,定义一个注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Safety {
}
然后分别对加解密拦截器进行改造,先改造解密拦截器,通过反射获取到返回实体的字段信息,然后对标有注解的字段进行解密处理:
public class DecryptInterceptor implements Interceptor {
...
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
List<Object> rows = (List<Object>) invocation.proceed();
rows.parallelStream().forEach(this::decrypt);
return rows;
}
private void decrypt(Object row){
MetaObject object = SystemMetaObject.forObject(row);
Field[] fields = row.getClass().getDeclaredFields();
Arrays.stream(fields)
.filter(field -> field.isAnnotationPresent(Safety.class))
.forEach(field -> doDecrypt(field, object));
}
private void doDecrypt(Field field, MetaObject object){
String name = field.getName();
Object value = object.getValue(name);
object.setValue(name, safetyCipher.decrypt(value));
}
}
接着同样,我们对加密进行修改
public class EncryptInterceptor implements Interceptor {
...
public Object intercept(Invocation invocation) throws Throwable {
Object param = invocation.getArgs()[1];
MetaObject meta = SystemMetaObject.forObject(param);
Field[] fields = param.getClass().getDeclaredFields();
Arrays.stream(fields)
.filter(field -> field.isAnnotationPresent(Safety.class))
.forEach(field -> doEncrypt(field, meta));
return invocation.proceed();
}
private void doEncrypt(Field field, MetaObject meta) {
String name = field.getName();
Object value = meta.getValue(name);
if (Objects.isNull(value)) return;
meta.setValue(name, safetyCipher.encrypt(value));
}
}
这样我就基本完成了基于注解的加解密实现了,但是这仍不完善,通常我们使用mybatis往往不会使用完整实体进行查询或者返回结果也可能是Map而非实体,后面我们在一步步的解决完善。