本文将介绍使用springboot+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)
CREATE TABLE `book` (
`userid` int(64) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`ustatus` varchar(255) DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;
代码部分
@Configuration
public class MyBatisConfig {
@Resource
private DecryptService decryptService;
@Resource
private EncryptService encryptService;
@Bean
public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addInterceptor(new EncryptInterceptor(encryptService));
sqlSessionFactory.getConfiguration().addInterceptor(new DecryptInterceptor(decryptService));
return "interceptor";
}
}
这里是3个注解,为了节约空间才写到这里一堆
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface DecryptField {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface EncryptField {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SensitiveData {
}
/**
* 查询解密
* 依赖注解 SensitiveData 与 SensitiveField
* 需要解密的类:类上需要标记SensitiveData 敏感属性 包括集合 需要添加SensitiveField
* 解释: @Intercepts 注解开启拦截器,@Signature 注解定义拦截器的实际类型。
*
* @Signature中 type 属性指定当前拦截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一种
* method 属性指定使用以上四种类型的具体方法(可进入class内部查看其方法)。
* args 属性指定预编译语句
*
* Interceptor 是Mybatis 的 拦截器接口
*/
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class EncryptInterceptor implements Interceptor {
private EncryptService encryptService;
public EncryptInterceptor(EncryptService encryptService) {
this.encryptService = encryptService;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
encryptService.encryptSensitiveField(parameterHandler);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
/**
* 里就是将拦截器和对象包装在一起:
* ①获取拦截器的Intercepts注解的所有Signature参数,即该拦截器要拦截的类和对象的方法、参数
* ②获取拦截对象的类
* ③获取所有接口
* ④根据返回的接口数量,判断是否要拦截的,要拦截的对象生成将拦截器和拦截对象封装在一起的代理对象
*/
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
/**
* 在MyBatis配置文件中配置插件时可以设置参数,在setProperties函数中调用 Properties.getProperty("param1")
* 方法可以得到配置的值
*/
}
}
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DecryptInterceptor implements Interceptor {
private DecryptService decryptService;
public DecryptInterceptor(DecryptService decryptService) {
this.decryptService = decryptService;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (null != result) {
decryptService.decryptSensitiveField(result);
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
这里是两个接口,为了节约空间才写到一起的。
public interface DecryptService {
void decryptSensitiveField(Object targetObj) throws Exception;
}
public interface EncryptService {
void encryptSensitiveField(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException;
}
@Service
public class DecryptServiceImpl implements DecryptService {
/**
* 配置在yaml的密钥
**/
@Value("${aes.key}")
private String key;
@Override
public void decryptSensitiveField(Object targetObj) throws Exception {
if (null == targetObj) {
return;
}
handleDecrypt(targetObj);
}
private void handleDecrypt(Object targetObj) throws Exception {
if (Objects.isNull(targetObj)) {
return;
}
String typeName = targetObj.getClass().getTypeName();
if (typeName.startsWith("com.atpingan")) {
doDecryptField(targetObj);
return;
}
handleDecryptCollection(targetObj);
}
/**
* 处理集合
*
* @param result
*/
private void handleDecryptCollection(Object result) throws Exception {
if (result instanceof List) {
handleDecryptList(result);
return;
}
if (result instanceof Map) {
handleDecryptMap(result);
return;
}
if (result instanceof Set) {
handleDecryptSet(result);
return;
}
}
private void handleDecryptSet(Object result) throws Exception {
if (ObjectUtils.isNotEmpty(result)) {
Set<Object> opSet = (HashSet) result;
if (ObjectUtils.isEmpty(opSet)) {
return;
}
for (Object item : opSet) {
if (ObjectUtils.isEmpty(item)) {
continue;
}
handleDecrypt(item);
}
}
}
private void handleDecryptMap(Object result) throws Exception {
if (ObjectUtils.isNotEmpty(result)) {
Map<String, Object> map = (HashMap) result;
if (ObjectUtils.isNotEmpty(map)) {
for (Map.Entry<String, Object> key : map.entrySet()) {
Object mapObject = map.get(key.getKey());
if (ObjectUtils.isEmpty(mapObject)) {
continue;
}
handleDecrypt(mapObject);
}
}
}
}
private void handleDecryptList(Object result) throws Exception {
if (ObjectUtils.isNotEmpty(result)) {
ArrayList<Object> list = (ArrayList) result;
Class<?> aClass = list.get(0).getClass();
if (!SensitiveUtil.isSensitiveClass(aClass)) {
return;
}
List<Field> sensitiveField = SensitiveUtil.getSensitiveField(aClass);
if (CollectionUtils.isNotEmpty(sensitiveField)) {
for (Object obj : list) {
doDecryptField(obj, sensitiveField);
}
}
}
}
private void doDecryptField(Object result) throws Exception {
Class<?> aClass = result.getClass();
if (!SensitiveUtil.isSensitiveClass(aClass)) {
return;
}
List<Field> sensitiveFields = SensitiveUtil.getSensitiveField(aClass);
if (CollectionUtils.isEmpty(sensitiveFields)) {
return;
}
doDecryptField(result, sensitiveFields);
}
private void doDecryptField(Object result, List<Field> sensitiveFields) throws Exception {
for (Field fs : sensitiveFields) {
/**
* 注:这里引用了hutool包 hutool-core
**/
Object fieldValue = ReflectUtil.getFieldValue(result, fs);
if (fieldValue instanceof String) {
if (ObjectUtils.isEmpty(fs)) {
continue;
}
String fieldValueStr = (String) fieldValue;
if (fieldValueStr.startsWith("W86z:")) {
fieldValueStr = AESUtil.decrypt(fieldValueStr.substring("W86z:".length()), key);
}
ReflectUtil.setFieldValue(result, fs, fieldValueStr);
} else {
//递归
handleDecrypt(fieldValue);
}
}
}
}
@Service
public class EncryptServiceImpl implements EncryptService {
@Value("${aes.key}")
private String aesKey;
@Override
public void encryptSensitiveField(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException {
Object parameterObject = parameterHandler.getParameterObject();
if (Objects.isNull(parameterObject)) {
return;
}
if (parameterObject instanceof MapperMethod.ParamMap) {
handleMethodInMapper(parameterHandler);
return;
}
handleMethodNotInmapper(parameterObject);
}
private void handleMethodInMapper(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException {
MapperMethod.ParamMap<Object> paramMap = ( MapperMethod.ParamMap<Object>) parameterHandler.getParameterObject();
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
String key = entry.getKey();
Object paramValue = entry.getValue();
if (!key.startsWith("param") && Objects.nonNull(paramValue)) {
encrypt(parameterHandler, entry);
}
}
}
/**
* 对DB字段进行加密
*
* @param parameterHandler
* @param entry
*/
private void encrypt(ParameterHandler parameterHandler, Map.Entry<String, Object> entry) throws NoSuchFieldException, ClassNotFoundException {
Object paramValue = entry.getValue();
//字符串
if (paramValue instanceof String) {
handleString(parameterHandler, entry);
return;
}
//请求参数不是字符串
handleMethodNotInmapper(paramValue);
}
/**
* 对象 只处理:1.包含SensitiveData注解,属性包含EncryptField注解 2.属于com.atpingan的包或子包
*
* @param targetObj
*/
private void handleMethodNotInmapper(Object targetObj) {
if (Objects.isNull(targetObj)) {
return;
}
Class<?> parameterClass = targetObj.getClass();
Object entity = targetObj;
Pair<Class<?>, Object> pair = handleQueryWrapper(targetObj);
boolean isQueryWrapperOrLambdaQueryWrapper = Objects.nonNull(pair);
if (isQueryWrapperOrLambdaQueryWrapper) {
parameterClass = pair.getLeft();
entity = pair.getRight();
}
if (Objects.isNull(entity)) {
return;
}
//是否敏感类
boolean sensitiveClass = SensitiveUtil.isSensitiveClass(parameterClass);
if (!sensitiveClass) {
return;
}
//路径前缀判断
String typeName = parameterClass.getTypeName();
if (!typeName.startsWith("com.atping")) {
return;
}
//获取敏感字段
List<Field> sensitiveFields = SensitiveUtil.getSensitiveField(parameterClass);
if (ObjectUtils.isEmpty(sensitiveFields)) {
return;
}
encryptField(entity, sensitiveFields);
if (isQueryWrapperOrLambdaQueryWrapper) {
ReflectUtil.setFieldValue(targetObj, "entity", entity);
}
}
private void encryptField(Object entity, List<Field> sensitiveFields) {
for (Field fs : sensitiveFields) {
Object fieldValue = ReflectUtil.getFieldValue(entity, fs);
if (fieldValue instanceof String) {
String fieldValueStr = (String) fieldValue;
ReflectUtil.setFieldValue(entity, fs, "W86z:"+AESUtil.encrypt(fieldValueStr, aesKey));
}
}
}
private Pair<Class<?>, Object> handleQueryWrapper(Object targetObj) {
if (targetObj instanceof LambdaQueryWrapper) {
return Pair.of(((LambdaQueryWrapper<?>) targetObj).getEntityClass(), ((LambdaQueryWrapper<?>) targetObj).getEntity());
}
if (targetObj instanceof LambdaUpdateWrapper) {
return Pair.of(((LambdaUpdateWrapper<?>) targetObj).getEntityClass(), ((LambdaUpdateWrapper<?>) targetObj).getEntity());
}
if (targetObj instanceof QueryWrapper) {
return Pair.of(((QueryWrapper<?>) targetObj).getEntityClass(), ((QueryWrapper<?>) targetObj).getEntity());
}
return null;
}
private void handleString(ParameterHandler parameterHandler, Map.Entry<String, Object> entry) throws NoSuchFieldException, ClassNotFoundException {
String key = entry.getKey();
Object paramValue = entry.getValue();
Class<? extends ParameterHandler> aClass = parameterHandler.getClass();
Field mappedStatement = aClass.getDeclaredField("mappedStatement");
ReflectUtil.setAccessible(mappedStatement);
MappedStatement statement = (MappedStatement) ReflectUtil.getFieldValue(parameterHandler, mappedStatement);
//方法命名空间
String nameSpace = statement.getId();
if (StringUtils.isBlank(nameSpace)) {
return;
}
String methodName = nameSpace.substring(nameSpace.lastIndexOf(".") + 1);
String className = nameSpace.substring(0, nameSpace.lastIndexOf("."));
Method[] ms = Class.forName(className).getMethods();
Optional<Method> optionalMethod = Arrays.stream(ms).filter(item -> StringUtils.equals(item.getName(), methodName)).findFirst();
if (!optionalMethod.isPresent()) {
return;
}
Method method = optionalMethod.get();
ReflectUtil.setAccessible(method);
//方法参数里面的请求参数注解列表
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
boolean sensitiveField = Arrays.stream(parameterAnnotations).anyMatch(item -> Boolean.TRUE.equals(Arrays.stream(item).anyMatch(ite -> {
if (ite instanceof EncryptField) {
EncryptField sensitive = (EncryptField) ite;
return StringUtils.equals(key, sensitive.value());
}
return false;
})));
if (!sensitiveField) {
return;
}
String encrypt = "W86z:"+AESUtil.encrypt((String) paramValue, aesKey);
entry.setValue(encrypt);
}
}
public class SensitiveUtil {
/**
* 判断一个类是否敏感类,即是否有注解 @SensitiveData
* @param clazz
* @return
*/
public static boolean isSensitiveClass(Class<?> clazz){
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(clazz,SensitiveData.class);
return ObjectUtils.isNotEmpty(sensitiveData);
}
/**
* 获取敏感类里面的敏感字段
* @param clazz
* @return
*/
public static List<Field> getSensitiveField(Class<?> clazz){
List<Field> list = new ArrayList<>();
Field[] fields = ReflectUtil.getFields(clazz);
for (Field fs: fields) {
boolean annotationPresent = fs.isAnnotationPresent(EncryptField.class);
if(annotationPresent){
list.add(fs);
}
}
return list;
}
}
@Slf4j
public class AESUtil {
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法
/**
* 敏感数据自定义前缀
*/
public static final String KEY_SENSITIVE = "W86z:";
/**
* AES 加密操作
*
* @param content 待加密内容
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String key) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
//Base64是一种基于64个可打印字符来表示二进制数据的表示方法。
return Base64Utils.encodeToString(result);//通过Base64转码返回
} catch (Exception ex) {
log.error(ex.getMessage());
}
return null;
}
/**
* AES 解密操作
*
* @param content
* @return
*/
public static String decrypt(String content, String key) {
try {
//实例化
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
//执行操作
//Base64是一种基于64个可打印字符来表示二进制数据的表示方法。
byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content));
return new String(result, StandardCharsets.UTF_8);
} catch (Exception ex) {
log.error(ex.getMessage());
}
return null;
}
/**
* 生成加密秘钥
* @return
*/
private static Key getSecretKey(String key) throws NoSuchAlgorithmException {
//返回生成指定算法密钥生成器的 KeyGenerator 对象
KeyGenerator kg = null;
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(key.getBytes());
try {
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//AES 要求密钥长度为 128
kg.init(128, random);
//生成一个密钥
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
} catch (NoSuchAlgorithmException ex) {
log.error(ex.getMessage());
}
return null;
}
}