最近老大想对储存到数据库的数据进行加密,不让某表的某几列以明文的形式储存,巧的是这些数据是公司框架写好的,不能直接上代码改。
而业务层对于这张表的数据,应用的地方非常多,从业务侧进行拦截不现实。
所以,决定指使本“怨种”编写 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.";// userfinal String getDetail = basePkg + "getDetail";final String get = basePkg + "get";USER_SET.add(getDetail);USER_SET.add(get);// userListfinal String getByUserName = basePkg + "getByUserName";final String listUserByResourceId = basePkg + "listUserByResourceId";USER_LIST_SET.add(getByUserName);USER_LIST_SET.add(listUserByResourceId);}@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("UserQueryInterceptor in");Object result = invocation.proceed();MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];// 获取执行idString 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 {@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("UserUpdateInterceptor in");MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];// 获取执行idString 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;}}