最近老大想对储存到数据库的数据进行加密,不让某表的某几列以明文的形式储存,巧的是这些数据是公司框架写好的,不能直接上代码改。
而业务层对于这张表的数据,应用的地方非常多,从业务侧进行拦截不现实。
所以,决定指使本“怨种”编写 DAO 层的拦截器,对特定输入、输出的几个数据进行加解密。
…经过两天的 coding 拦截器完成度 80% 。
此时,提了一句“加密后的参数无法使用“模糊查询”,而且拦截的方法特别多”。老大大意回了句,先这样吧,弄别的先,看看别的有没有解决方案…
我…就知道~呵,DAO 层拦截器不被普遍使用是有原因的,一旦使用这项技术大概率是某个乌龙事件发生了~
查询拦截器
- 获取 id
- 比对 DAO 的全量方法名 com.xxx.xx.methedName 判断,是否是需要处理的方法
- 获取参数,判断、强转 参数为 bean
- 处理参数,参数替换(原对象的参数是不可变的需要赋值替换)
- 执行“业务逻辑”
- 处理响应
- 结束
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* 用户查询拦截器
*
*/
@Component
@Intercepts({
@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}
)
})
@Slf4j
public class UserQueryInterceptor implements Interceptor {
/**
* 需要拦截的 DAO 层查询方法
* 相同 set 代表相同响应 DTO
*/
private static final Set<String> USER_SET = new HashSet<>();
private static final Set<String> USER_LIST_SET = new HashSet<>();
static {
final String basePkg = "com.xxx.xx.manage.user.core.dao.UserDao.";
// user
final String getDetail = basePkg + "getDetail";
final String get = basePkg + "get";
USER_SET.add(getDetail);
USER_SET.add(get);
// userList
final String getByUserName = basePkg + "getByUserName";
final String listUserByResourceId = basePkg + "listUserByResourceId";
USER_LIST_SET.add(getByUserName);
USER_LIST_SET.add(listUserByResourceId);
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("UserQueryInterceptor in");
Object result = invocation.proceed();
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取执行id
String id = mappedStatement.getId();
log.info("id : {}", id);
// 处理查询参数
processQueryParams(invocation, id);
// 处理响应
processResult(id, result);
log.info("UserQueryInterceptor end");
return result;
}
/**
* 处理查询参数
*
* @param invocation invocation
* @param id id
*/
private void processQueryParams(Invocation invocation, String id) {
//todo:加密后的参数无法使用模糊查询
}
/**
* 处理响应
*
* @param id
* @param result
*/
private void processResult(String id, Object result) {
if (ObjectUtils.isNull(result)) {
return;
}
if (USER_SET.contains(id)) {
User user = (User) result;
decode(user);
}
if (USER_LIST_SET.contains(id)) {
List<User> list = (List<User>) result;
for (User user : list) {
decode(user);
}
}
}
/**
* 解密
*
* @param user user
*/
private void decode(User user) {
if (null == user) {
return;
}
// todo 解密
user.setName(decode(user.getName());
}
/**
* 解密
*
* @param data 密文
* @return 原文
*/
private String decode(String data) {
// todo 解密
return null;
}
}
更新拦截器
- 获取 id
- 比对 DAO 的全量方法名 com.xxx.xx.methedName 判断,是否是需要处理的方法
- 获取参数,判断、强转 参数
- 处理参数,参数替换(原对象的参数是不可变的需要赋值替换)
- 执行“业务逻辑”
- 结束
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* 用户插入、更新查询拦截器
*
*/
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class UserUpdateInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("UserUpdateInterceptor in");
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取执行id
String id = mappedStatement.getId();
log.info("id : {}", id);
// 获取参数
Object params;
if (invocation.getArgs().length > 1) {
params = invocation.getArgs()[1];
} else {
return invocation.proceed();
}
log.info("params {}", params);
// 处理目标参数
processTargetParams(invocation, id, params);
log.info("UserUpdateInterceptor end");
return invocation.proceed();
}
/**
* 处理目标参数
*
* @param invocation invocation
* @param id id id参见UserDao
* @param params 参数
* @see UserDao
*/
private void processTargetParams(Invocation invocation, String id, Object params) {
final String insert = "com.xxx.xx.manage.user.core.dao.UserDao.insert";
final String update = "com.xxx.xx.manage.user.core.dao.UserDao.update";
final String insertBatch = "com.xxx.xx.manage.user.core.dao.UserDao.insertBatch";
switch (id) {
// 插入
case insert -> {
User insertUser = (User) params;
if (null != insertUser) {
encodeUserInfo(insertUser);
invocation.getArgs()[1] = insertUser;
}
log.info("insertUser encode : {}", insertUser);
}
// 更新
case update -> {
User updateUser = (User) params;
if (null != updateUser) {
encodeUserInfo(updateUser);
invocation.getArgs()[1] = updateUser;
}
log.info("updateUser encode : {}", updateUser);
}
// 批量插入
case insertBatch -> {
List<User> userList = (List<User>) params;
log.info("insertBatch encode : {}", userList);
if (!CollectionUtils.isEmpty(userList)) {
for (User user : userList) {
encodeUserInfo(user);
}
invocation.getArgs()[1] = userList;
}
log.info("updateUser encode : {}", userList);
}
default -> {
}
}
}
/**
* 用户信息加密
*
* @param user 用户信息
*/
private void encodeUserInfo(User user) {
if (StringUtils.isNotEmpty(user.getName())) {
user.setName(encode(user.getName()));
}
}
/**
* 加密
* 注意长度不能超过数据库允许的长度
*
* @param data 数据
* @return 加密后的数据
*/
private String encode(String data) {
// todo 加密
return null;
}
}