问题
在我的项目中须要对用户敏感数据进行脱敏解决,例如身份号、手机号等信息进行加密再入库。
解决思路
- 就是:一种最简略间接的形式,在所有波及数据敏感的查问到对插入时进行明码加解密
- 办法二:有办法一到呈现对所有重大问题的影响,须要思考到问题的呈现,并且须要思考可能呈现的组员时增加数据的办法。
最初决定采纳mybatis的插件在mybatis的SQL执行和后果填充操作上进行切入。下层业务调用不再须要思考数据的加敏同时也保障了数据的加解密
Mybatis 插件原理
Mybatis 的是通过拦截器实现的,Mabatis 反对对当事人进行拦挡
实现
- 设置对参数中带有敏感参数字段的数据时进行加密
- 对返回的后果进行解密解决
依据不同的要求,咱们只须要对ParameterHandler
和ResultSetHandler
进行切入。
定义特定注解,在切入时须要查看字段中是否蕴含注解来是否加解密
加注解
定义SensitiveData
注解
import java.lang.annotation.*;/** * 该注解定义在类上 * 插件通过扫描类对象是否蕴含这个注解来决定是否持续扫描其中的字段注解 * 这个注解要配合EncryptTransaction注解 **/@Inherited@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface SensitiveData {}
定义EncryptTransaction
注解
import java.lang.annotation.*;/** * 该注解有两种应用形式 * ①:配合@SensitiveData加在类中的字段上 * ②:间接在Mapper中的办法参数上应用 **/@Documented@Inherited@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)public @interface EncryptTransaction {}
加解密工具类
解密
package sicnu.cs.ich.common.interceptor.transaction.service;public interface IDecryptUtil { /** * 解密 * * @param result resultType的实例 * @return T * @throws IllegalAccessException 字段不可拜访异样 */ <T> T decrypt(T result) throws IllegalAccessException;}
加密接口
package sicnu.cs.ich.common.interceptor.transaction.service;import java.lang.reflect.Field;public interface IEncryptUtil { /** * 加密 * * @param declaredFields 加密字段 * @param paramsObject 对象 * @param <T> 入参类型 * @return 返回加密 * @throws IllegalAccessException 不可拜访 */ <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;}package sicnu.cs.ich.common.interceptor.transaction.service.impl;import org.springframework.stereotype.Component;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;import sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil;import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;import java.lang.reflect.Field;import java.util.Objects;@Componentpublic class DecryptImpl implements IDecryptUtil { /** * 解密 * * @param result resultType的实例 */ @Override public <T> T decrypt(T result) throws IllegalAccessException { //取出resultType的类 Class<?> resultClass = result.getClass(); Field[] declaredFields = resultClass.getDeclaredFields(); for (Field field : declaredFields) { //取出所有被DecryptTransaction注解的字段 EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class); if (!Objects.isNull(encryptTransaction)) { field.setAccessible(true); Object object = field.get(result); //String的解密 if (object instanceof String) { String value = (String) object; //对注解的字段进行逐个解密 try { field.set(result, DBAESUtil.decrypt(value)); } catch (Exception e) { e.printStackTrace(); } } } } return result; }}
加密实现类
package sicnu.cs.ich.common.interceptor.transaction.service.impl;import com.fasterxml.jackson.databind.ObjectReader;import org.springframework.stereotype.Component;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil;import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;import java.io.ObjectInputStream;import java.lang.reflect.Field;import java.util.Arrays;import java.util.Objects;import java.util.Random;@Componentpublic class EncryptUtilImpl implements IEncryptUtil { @Override public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException { //取出所有被EncryptTransaction注解的字段 for (Field field : declaredFields) { EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class); if (!Objects.isNull(encryptTransaction)) { field.setAccessible(true); Object object = field.get(paramsObject); //临时只实现String类型的加密 if (object instanceof String) { String value = (String) object; //加密 try { field.set(paramsObject, DBAESUtil.encrypt(value)); } catch (Exception e) { e.printStackTrace(); } } } } return paramsObject; }}
模仿类
package sicnu.cs.ich.common.interceptor.transaction.service.impl;import org.springframework.stereotype.Component;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;import sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil;import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;import java.lang.reflect.Field;import java.util.Objects;@Componentpublic class DecryptImpl implements IDecryptUtil { /** * 解密 * * @param result resultType的实例 */ @Override public <T> T decrypt(T result) throws IllegalAccessException { //取出resultType的类 Class<?> resultClass = result.getClass(); Field[] declaredFields = resultClass.getDeclaredFields(); for (Field field : declaredFields) { //取出所有被DecryptTransaction注解的字段 EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class); if (!Objects.isNull(encryptTransaction)) { field.setAccessible(true); Object object = field.get(result); //String的解密 if (object instanceof String) { String value = (String) object; //对注解的字段进行逐个解密 try { field.set(result, DBAESUtil.decrypt(value)); } catch (Exception e) { e.printStackTrace(); } } } } return result; }}
加解密工具类
package sicnu.cs.ich.common.util.keyCryptor;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.util.Base64;public class DBAESUtil { private static final String DEFAULT_V = "6859505890402435"; // 本人填写 private static final String KEY = "***"; private static final String ALGORITHM = "AES"; private static SecretKeySpec getKey() { byte[] arrBTmp = DBAESUtil.KEY.getBytes(); // 创立一个空的16位字节数组(默认值为0) byte[] arrB = new byte[16]; for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) { arrB[i] = arrBTmp[i]; } return new SecretKeySpec(arrB, ALGORITHM); } /** * 加密 */ public static String encrypt(String content) throws Exception { final Base64.Encoder encoder = Base64.getEncoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); byte[] encrypted = cipher.doFinal(content.getBytes()); return encoder.encodeToString(encrypted); } /** * 解密 */ public static String decrypt(String content) throws Exception { final Base64.Decoder decoder = Base64.getDecoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); byte[] base64 = decoder.decode(content); byte[] original = cipher.doFinal(base64); return new String(original); }}
举荐一个开源收费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
插件实现
参数插件ParameterInterceptor
切入mybatis设置参数时对敏感数据进行加密
Mybatis插件的应用就是通过实现Mybatis中的Interceptor
接口
再@Intercepts
注解
// 应用mybatis插件时须要定义签名// type标识须要切入的Handler// method示意要要切入的办法@Intercepts({@Signature(type = ParameterHandler.class, method = “setParameters”, args = PreparedStatement.class),})package sicnu.cs.ich.common.interceptor.transaction;import com.baomidou.mybatisplus.core.MybatisParameterHandler;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil;import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Parameter;import java.sql.PreparedStatement;import java.util.*;@Slf4j// 注入Spring@Component@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),})public class ParameterInterceptor implements Interceptor { @Autowired private IEncryptUtil IEncryptUtil; @Override public Object intercept(Invocation invocation) throws Throwable { //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler //若指定ResultSetHandler ,这里则能强转为ResultSetHandler MybatisParameterHandler parameterHandler = (MybatisParameterHandler) invocation.getTarget(); // 获取参数对像,即 mapper 中 paramsType 的实例 Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); parameterField.setAccessible(true); //取出实例 Object parameterObject = parameterField.get(parameterHandler); // 搜寻该办法中是否有须要加密的一般字段 List<String> paramNames = searchParamAnnotation(parameterHandler); if (parameterObject != null) { Class<?> parameterObjectClass = parameterObject.getClass(); //对类字段进行加密 //校验该实例的类是否被@SensitiveData所注解 SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class); if (Objects.nonNull(sensitiveData)) { //取出以后以后类所有字段,传入加密办法 Field[] declaredFields = parameterObjectClass.getDeclaredFields(); IEncryptUtil.encrypt(declaredFields, parameterObject); } // 对一般字段进行加密 if (!CollectionUtils.isEmpty(paramNames)) { // 反射获取 BoundSql 对象,此对象蕴含生成的sql和sql的参数map映射 Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql"); boundSqlField.setAccessible(true); BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler); PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0]; // 改写参数 processParam(parameterObject, paramNames); // 改写的参数设置到原parameterHandler对象 parameterField.set(parameterHandler, parameterObject); parameterHandler.setParameters(ps); } } return invocation.proceed(); } private void processParam(Object parameterObject, List<String> params) throws Exception { // 解决参数对象 如果是 map 且map的key 中没有 tenantId,增加到参数map中 // 如果参数是bean,反射设置值 if (parameterObject instanceof Map) { @SuppressWarnings("unchecked") Map<String, String> map = ((Map<String, String>) parameterObject); for (String param : params) { String value = map.get(param); map.put(param, value==null?null:DBAESUtil.encrypt(value)); }// parameterObject = map; } } private List<String> searchParamAnnotation(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException { Class<MybatisParameterHandler> handlerClass = MybatisParameterHandler.class; Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement"); mappedStatementFiled.setAccessible(true); MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler); String methodName = mappedStatement.getId(); Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.'))); methodName = methodName.substring(methodName.lastIndexOf('.') + 1); Method[] methods = mapperClass.getDeclaredMethods(); Method method = null; for (Method m : methods) { if (m.getName().equals(methodName)) { method = m; break; } } List<String> paramNames = null; if (method != null) { Annotation[][] pa = method.getParameterAnnotations(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < pa.length; i++) { for (Annotation annotation : pa[i]) { if (annotation instanceof EncryptTransaction) { if (paramNames == null) { paramNames = new ArrayList<>(); } paramNames.add(parameters[i].getName()); } } } } return paramNames; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { }}
返回值插件ResultSetInterceptor
package sicnu.cs.ich.common.interceptor.transaction;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.executor.resultset.ResultSetHandler;import org.apache.ibatis.plugin.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;import java.sql.Statement;import java.util.ArrayList;import java.util.Objects;import java.util.Properties;@Slf4j@Component@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})public class ResultSetInterceptor implements Interceptor { @Autowired private sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil IDecryptUtil; @Override public Object intercept(Invocation invocation) throws Throwable { //取出查问的后果 Object resultObject = invocation.proceed(); if (Objects.isNull(resultObject)) { return null; } //基于selectList if (resultObject instanceof ArrayList) { @SuppressWarnings("unchecked") ArrayList<Objects> resultList = (ArrayList<Objects>) resultObject; if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) { for (Object result : resultList) { //逐个解密 IDecryptUtil.decrypt(result); } } //基于selectOne } else { if (needToDecrypt(resultObject)) { IDecryptUtil.decrypt(resultObject); } } return resultObject; } private boolean needToDecrypt(Object object) { Class<?> objectClass = object.getClass(); SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class); return Objects.nonNull(sensitiveData); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { }}
应用
留神解在实体类上
import lombok.*;import org.springframework.security.core.userdetails.UserDetails;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;@With@Builder@Data@NoArgsConstructor@AllArgsConstructor@SensitiveData // 插件只对加了该注解的类进行扫描,只有加了这个注解的类才会失效public class User implements Serializable { private Integer id; private String username; private String openId; private String password; // 表明对该字段进行加密 @EncryptTransaction private String email; // 表明对该字段进行加密 @EncryptTransaction private String mobile; private Date createTime; private Date expireTime; private Boolean status = true;}
注解在参数上
import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;@Mapperpublic interface UserMapper extends BaseMapper<User> { // 只须要在参数前加上@EncryptTransaction 即可 long countByEmail(@EncryptTransaction @Param("email") String email); long countByMobile(@EncryptTransaction @Param("mobile") String mobile);}
版权申明:本文为CSDN博主「shenyang1026」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。
原文链接:https://blog.csdn.net/relosy/article/details/123494036
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!