本文主要是介绍mybatis用拦截器实现字段加解密全过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《mybatis用拦截器实现字段加解密全过程》本文通过自定义注解和MyBatis拦截器实现敏感信息加密,处理Parameter和ResultSet,确保数据库存储安全且查询结果解密可用...
前言
根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用myBATis拦截器进行简单实现个demo。
拦截器的使用
// 执行 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) // 请求参数处理 ParameterHandler (getParameterObject, setParameters) // 返回结果集处理 ResultSetHandler (handleResultSets, handleOutputParameters) // SQL语句构建 StatementHandler (prepare, parameterize, batch, update, query)
我们要实现数据加密,进入数据库的字段不能是真实的数据,但是返回来的数据要真实可用,所以我们需要针对 Parameter 和 ResultSet 两种类型处理,同时为了更灵活的使用,我们需要自定义注解。
/** *需要加解密的字段注解 **/ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Encryption { String encryptionType() default ""; }
编写一下加解密算法(随便找的)
** * @desc: AES对称加密,对明文进行加密、解密处理 * @author: * @createTime: 20231014 上午9:54:52 * @version: v0.0.1 */ public class AESUtil { private static final String KEY_ALGORITHM = "AES"; private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; /** * @desc: AES对称-加密操作 * @version: v0.0.1 * @param keyStr 进行了Base64编码的秘钥 * @param data 需要进行加密的原文 * @return String 数据密文,加密后的数据,进行了Base64的编码 */ public static String encrypt(String keyStr, String data) throws Exception { // 转换密钥 Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); // 加密 cipher.init(Cipher.ENCRYPT_MODE, key); byte[] result = cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(result); } /** * @desc: AES对称-解密操作 * @version: v0.0.1 * @param keyStr 进行了Base64编码的秘钥 * @param data 需要解密的数据<span>(数据必须是通过AES进行加密后,对加密数据Base64编码的数据)</span> * @return String 返回解密后的原文 */ public static String decrypt(String keyStr, Standroidring data) throws Exception { // 转换密钥 Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); php // 解密 cipher.init(Cipher.DECRYPT_MODE, key); byte[] result = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(result); } /** * @desc: 生成AES的秘钥,秘钥进行了Base64编码的字符串 * @version: v0.0.1 * @return String 对生成的秘钥进行了Base64编码的字符串 */ public static String keyGenerate() throws Exception { // 生成密钥 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); byte[] keyBytes = secretKey.getEncoded(); return Base64.getEncoder().encodeToString(keyBytes); } public static void main(String[] args) throws Exception { System.out.println( keyGenerate() ); } }
编写一下加解密接口
/** * 加解密处理接口 */ public interface CipherHandler { /** * 加密 * @param data 需要加密的数据 * @return 加密结果 */ String encrypt(String data) throws Exception; /** * 解密 * @param data 需要加密的数据 * @return 解密结果 */ String decrypt(String data) throws Exception; }
public class AEScipher implements CipherHandler{ private final static String keyStr="glRwFSBcKzppYVQiwT8M/Q=="; @Override public String encrypt(String data) throws Exception { return AESUtil.encrypt(this.keyStr,data); } @Override public String decrypt(String data) throws Exception { return AESUtil.decrypt(this.keyStr,data); } public static CipherHandler getAEScipher(){ return new AEScipher(); } }
接下来写一下加解密的拦截器
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class), }) @Component @Slf4j public class ParameterInterceptor implements Interceptor { private CipherHandler cipherHandler = AEScipher.getAEScipher(); @Override public Object intercept(Invocation invocation) throws Throwable { DefaultParameterHandler parameterHandler = (DefaultParameterHandler) invocation.getTarget(); // 获取参数对像,即 mapper 中 paramsType 的实例 Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); gNwHbs parameterField.setAccessible(true); // 取出实例 Object parameterObject = parameterField.get(parameterHandler); try { // 搜索该方法中是否有需要加密的字段 List<Field> fieldList = searchParamAnnotation(parameterHandler); //加密 if(!CommonHelper.isEmpty(fieldList)){ dealParamEncrypt(fieldList,parameterObject); } parameterField.set(parameterHandler, parameterObject); PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0]; parameterHandler.setParameters(ps); } catch (Exception e) { log.info(e.getMessage()); } return invocation.proceed(); } /** * 查找需要需要加密字段 * @param parameterHandler * @return * @throws Exception */ private List<Field> searchParamAnnotation(ParameterHandler parameterHandler) throws Exception { Class<DefaultParameterHandler> handlerClass = DefaultParameterHandler.class; Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement"); mappedStatementFiled.setAccessible(true); MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler); String methodName = mappedStatement.getId(); // 获取Mapper类对象 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<Field&gChina编程t; fieldList = new ArrayList<>(); if (method != null) { Annotation[][] pa = method.getParameterAnnotations(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < pa.length; i++) { Parameter parameter = parameters[i]; String typeName = parameter.getParameterizedType().getTypeName(); // 去除泛型导致的ClassNotFoundException Class<?> parameterClass = Class.forName(typeName.contains("<") ? typeName.substring(0, typeName.indexOf("<")) : typeName); Field[] declaredFields = parameterClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Annotation annotation = declaredField.getAnnotation(Encryption.class); if(!CommonHelper.isEmpty(annotation))fieldList.add(declaredField); } } } return fieldList; } /** * 处理加密类 * @param fields * @param parameterObject * @throws Exception */ private void dealParamEncrypt(List<Field> fields,Object parameterObject) throws Exception { fields.forEach(declaredField->{ declaredField.setAccessible(true); Object o = null; try { o = declaredField.get(parameterObject); declaredField.set(parameterObject,cipherHandler.encrypt(o.toString())); } catch (Exception e) { throw new RuntimeException(e); } }); } @Override public Object plugin(Object target) { return Plugin.wrap(target,this); } @Override public void setProperties(Properties properties) { } }
查询结果解密
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) @Component public class ResultSetInterceptor implements Interceptor { private CipherHandler cipherHandler = AEScipher.getAEScipher(); @Override public Object intercept(Invocation invocation) throws Throwable { // 取出查询的结果 Object resultObject = invocation.proceed(); if (Objects.isNull(resulwww.chinasem.cntObject)) { return null; } // 基于selectList if (resultObject instanceof List<?>) { List<?> resultList = (List<?>) resultObject; if (!CommonHelper.isEmpty(resultList)) { for (Object obj : resultList) { toDecrypt(obj); } } } else { toDecrypt(resultObject); } return resultObject; } private void toDecrypt(Object object) throws Exception { Class<?> objectClass = object.getClass(); Field[] declaredFields = objectClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Annotation annotation = declaredField.getAnnotation(Encryption.class); if(!CommonHelper.isEmpty(annotation)){ declaredField.setAccessible(true); declaredField.set(object,cipherHandler.decrypt(declaredField.get(object).toString())); } } } @Override public Object plugin(Object o) { return Plugin.wrap(o,this); } @Override public void setProperties(Properties properties) { } }
判空工具类
public class CommonHelper { public static boolean isEmpty(Object o){ if(o==null)return true; if(o instanceof String){ return ((String) o).isEmpty(); }else if(o instanceof List){ return CollectionUtils.isEmpty((List) o) || ((List<?>) o).size()==0 ; }else if(o instanceof Map){ return CollectionUtils.isEmpty((Map) o) || ((Map<?,?>) o).size()==0 ; }else { return Objects.isNull(o); } } }
测试结果:
- 插入数据
- 查询数据:
总结
这篇关于mybatis用拦截器实现字段加解密全过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!