diff --git a/.gitignore b/.gitignore index 5d947ca8879f8a9072fe485c566204e3c2929e80..e7266ca6808db1c53e042f2e3ebc1e7d9468453c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ bin-release/ # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` # should NOT be excluded as they contain compiler settings and other important # information for Eclipse / Flash Builder. +/.idea/ diff --git a/pom.xml b/pom.xml index 1657fd2fc1d7ac6d107bd1e98e832a33b44898dd..581a0746fa83c406fb3ac758165492f13c1e16b2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,55 +6,37 @@ org.dflish mybatis-encrypt-plugins - 1.0-SNAPSHOT + 0.1-SNAPSHOT 1.8 1.8 UTF-8 - 8 + 1.8 - 2.2.8.RELEASE + 2.1.18.RELEASE 5.4.2 - - 3.3.1 - + 3.4.5 + 3.1 - - - - cn.hutool - hutool-all - ${hutool.version} - - org.springframework.boot spring-boot-dependencies - ${spring-boot-dependencies.version} + ${spring-boot.version} pom import - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus-boot-starter.version} - - - mysql mysql-connector-java @@ -76,6 +58,11 @@ true + + org.springframework.boot + spring-boot-autoconfigure + + cn.hutool @@ -89,45 +76,84 @@ test - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus-boot-starter.version} + com.github.jsqlparser + jsqlparser + ${jsqlparser.version} + + + + org.mybatis + mybatis + ${mybatis.version} + true - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot-dependencies.version} - - - + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + rebel.xml + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + - - - - aliyun-repos - https://maven.aliyun.com/repository/public - - false - - - - - huaweicloud - https://mirrors.huaweicloud.com/repository/maven/ - - false - - - - - \ No newline at end of file diff --git a/src/main/java/org/dflish/mybatis/encrypt/annotation/DecryptTransaction.java b/src/main/java/org/dflish/mybatis/encrypt/annotation/DecryptTransaction.java new file mode 100644 index 0000000000000000000000000000000000000000..b5993d03233e462c83a4b74766928b42e8c960e1 --- /dev/null +++ b/src/main/java/org/dflish/mybatis/encrypt/annotation/DecryptTransaction.java @@ -0,0 +1,36 @@ +package org.dflish.mybatis.encrypt.annotation; + +import org.dflish.mybatis.encrypt.util.DesensitizedType; + +import java.lang.annotation.*; + + +/** + * 解密注解 + * + * @author song_jx + * @date 2021-09-04 03:42:44 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DecryptTransaction { + + /** + * 数据是否需要脱敏,默认不脱敏 + * + * @return @return boolean + * @author song_jx + */ + boolean isDesensitized() default false; + + /** + * 数据脱敏类型,默认手机号脱敏 + * + * @return @return {@link DesensitizedType } + * @author song_jx + */ + DesensitizedType desensitizedType() default DesensitizedType.MOBILE_PHONE; + +} \ No newline at end of file diff --git a/src/main/java/org/dflish/mybatis/encrypt/annotation/SensitiveData.java b/src/main/java/org/dflish/mybatis/encrypt/annotation/SensitiveData.java new file mode 100644 index 0000000000000000000000000000000000000000..a4ed5f16adb3ab20e90ec90368d74a8ec49d1510 --- /dev/null +++ b/src/main/java/org/dflish/mybatis/encrypt/annotation/SensitiveData.java @@ -0,0 +1,15 @@ +package org.dflish.mybatis.encrypt.annotation; + +import java.lang.annotation.*; + +/** + * 敏感数据 + * + * @author song_jx + * @date 2021-09-04 03:56:30 + */ +@Inherited +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SensitiveData { +} \ No newline at end of file diff --git a/src/main/java/org/dflish/mybatis/encrypt/config/MybatisEncryptAutoConfigure.java b/src/main/java/org/dflish/mybatis/encrypt/config/MybatisEncryptAutoConfigure.java index 2ac9ddb539fcc14c49a707568026606fe38697d3..280cc41d8ced35caf4299daa0b134d46ab6846e5 100644 --- a/src/main/java/org/dflish/mybatis/encrypt/config/MybatisEncryptAutoConfigure.java +++ b/src/main/java/org/dflish/mybatis/encrypt/config/MybatisEncryptAutoConfigure.java @@ -1,7 +1,8 @@ package org.dflish.mybatis.encrypt.config; import lombok.AllArgsConstructor; -import org.dflish.mybatis.encrypt.plugins.EnCryptInterceptor; +import org.dflish.mybatis.encrypt.plugins.DecryptInterceptor; +import org.dflish.mybatis.encrypt.plugins.EncryptInterceptor; import org.dflish.mybatis.encrypt.properties.EnCryptProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -22,7 +23,13 @@ public class MybatisEncryptAutoConfigure { private final EnCryptProperties enCryptProperties; @Bean - public EnCryptInterceptor enCryptInterceptor() { - return new EnCryptInterceptor(enCryptProperties); + public EncryptInterceptor enCryptInterceptor() { + return new EncryptInterceptor(enCryptProperties); } + + @Bean + public DecryptInterceptor decryptInterceptor() { + return new DecryptInterceptor(enCryptProperties); + } + } diff --git a/src/main/java/org/dflish/mybatis/encrypt/core/CCJSQLStatementContext.java b/src/main/java/org/dflish/mybatis/encrypt/core/CCJSQLStatementContext.java index 31ead5a657d521ad674be585842b12c9606ad6ad..80a57305e6ec4d9298aeb1bbe56aba65a68c6204 100644 --- a/src/main/java/org/dflish/mybatis/encrypt/core/CCJSQLStatementContext.java +++ b/src/main/java/org/dflish/mybatis/encrypt/core/CCJSQLStatementContext.java @@ -2,10 +2,13 @@ package org.dflish.mybatis.encrypt.core; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; import lombok.Data; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.JdbcParameter; +import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; @@ -27,6 +30,8 @@ import java.util.*; @Data public class CCJSQLStatementContext { + private static final Log log = LogFactory.get(); + private Statement statements; private List tableList; @@ -112,24 +117,38 @@ public class CCJSQLStatementContext { PlainSelect plain = (PlainSelect) select.getSelectBody(); Expression where = plain.getWhere(); - where.accept(new ExpressionDeParser(null, b) { - @Override - public void visit(EqualsTo equalsTo) { - Column leftExpression = (Column) equalsTo.getLeftExpression(); - - Expression rightExpression = equalsTo.getRightExpression(); - - if (rightExpression instanceof JdbcParameter){ - JdbcParameter jdbcParameter = (JdbcParameter) rightExpression; - Integer index = jdbcParameter.getIndex(); - CCJSQLCryptExpressionDTO dto = new CCJSQLCryptExpressionDTO(); - dto.setAlias(TablesNamesFinderPlus.getAlias(leftExpression.getTable())); - dto.setColumnName(leftExpression.getColumnName()); - dto.setIndex(index); - list.add(dto); + if (null != where) { + where.accept(new ExpressionDeParser(null, b) { + @Override + public void visit(EqualsTo equalsTo) { + Expression leftExp = equalsTo.getLeftExpression(); + // 判断 1 = 1 情况 + boolean isLongValue = leftExp instanceof LongValue; + Column leftExpression = null; + if (!isLongValue) { + leftExpression = (Column) leftExp; + } + + Expression rightExpression = equalsTo.getRightExpression(); + if (null != leftExpression && rightExpression instanceof JdbcParameter) { + JdbcParameter jdbcParameter = (JdbcParameter) rightExpression; + Integer index = jdbcParameter.getIndex(); + CCJSQLCryptExpressionDTO dto = new CCJSQLCryptExpressionDTO(); + String alias = TablesNamesFinderPlus.getAlias(leftExpression.getTable()); + if (StrUtil.isBlank(alias)) { + log.error("---> [Mybatis 加密拦截器] 字段 {} 未设置归属表别名,默认使用 t 做为字段归属表的别名...", + leftExpression.getColumnName()); + dto.setAlias("t"); + } else { + dto.setAlias(alias); + } + dto.setColumnName(leftExpression.getColumnName()); + dto.setIndex(index); + list.add(dto); + } } - } - }); + }); + } return list; } diff --git a/src/main/java/org/dflish/mybatis/encrypt/plugins/DecryptInterceptor.java b/src/main/java/org/dflish/mybatis/encrypt/plugins/DecryptInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..b79f92937083fe84232713bd215f2ddd0c903d92 --- /dev/null +++ b/src/main/java/org/dflish/mybatis/encrypt/plugins/DecryptInterceptor.java @@ -0,0 +1,103 @@ +package org.dflish.mybatis.encrypt.plugins; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.dflish.mybatis.encrypt.annotation.SensitiveData; +import org.dflish.mybatis.encrypt.properties.EnCryptProperties; +import org.dflish.mybatis.encrypt.util.DecryptUtil; + +import java.lang.reflect.Proxy; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 解密拦截器 + * + * @author song_jx + * @date 2021-09-04 03:28:50 + */ +@Intercepts({ + @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) +}) +public class DecryptInterceptor implements Interceptor { + + private static final Log log = LogFactory.get(); + + private final EnCryptProperties properties; + + public DecryptInterceptor(EnCryptProperties properties) { + this.properties = properties; + } + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 取出查询的结果 + Object resultObject = invocation.proceed(); + + if (ObjectUtil.isNull(resultObject)) { + return null; + } + + if (resultObject instanceof ArrayList) { + // 基于selectList + List resultList = (ArrayList) resultObject; + if (CollUtil.isNotEmpty(resultList) && isNeedDecrypt(resultList.get(0))) { + for (Object result : resultList) { + //逐一解密 + DecryptUtil.decrypt(result, properties.getKey()); + } + } + } else { + // 基于selectOne + if (isNeedDecrypt(resultObject)) { + DecryptUtil.decrypt(resultObject, properties.getKey()); + } + } + + return resultObject; + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } + + /** + *

+ * 获得真正的处理对象,可能多层代理. + *

+ */ + private Object realTarget(Object target) { + if (Proxy.isProxyClass(target.getClass())) { + MetaObject metaObject = SystemMetaObject.forObject(target); + return realTarget(metaObject.getValue("h.target")); + } + return target; + } + + /** + * 是否需要解密 + * + * @param object 对象 + * @return @return boolean + * @author song_jx + */ + private boolean isNeedDecrypt(Object object) { + return AnnotationUtil.hasAnnotation(object.getClass(), SensitiveData.class); + } + +} diff --git a/src/main/java/org/dflish/mybatis/encrypt/plugins/EnCryptInterceptor.java b/src/main/java/org/dflish/mybatis/encrypt/plugins/EncryptInterceptor.java similarity index 59% rename from src/main/java/org/dflish/mybatis/encrypt/plugins/EnCryptInterceptor.java rename to src/main/java/org/dflish/mybatis/encrypt/plugins/EncryptInterceptor.java index cf55ce64c0813f57604af762ae1b3e627699adf2..18b68821610f58fc18cccccad2410ab7bbee33fc 100644 --- a/src/main/java/org/dflish/mybatis/encrypt/plugins/EnCryptInterceptor.java +++ b/src/main/java/org/dflish/mybatis/encrypt/plugins/EncryptInterceptor.java @@ -1,41 +1,43 @@ package org.dflish.mybatis.encrypt.plugins; -import com.baomidou.mybatisplus.core.toolkit.PluginUtils; -import lombok.extern.slf4j.Slf4j; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; -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.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.dflish.mybatis.encrypt.properties.EnCryptProperties; import org.dflish.mybatis.encrypt.rewrite.EncryptPreParameterRewriter; +import java.lang.reflect.Proxy; import java.sql.Statement; import java.util.List; +import java.util.Properties; /** + * 加密拦截器 + * * @author liangwx + * @date 2021-09-04 03:28:58 */ -@Slf4j @Intercepts({ @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class) }) -public class EnCryptInterceptor implements Interceptor { +public class EncryptInterceptor implements Interceptor { + + private static final Log log = LogFactory.get(); private final EncryptPreParameterRewriter rewriter; - public EnCryptInterceptor(EnCryptProperties properties) { + public EncryptInterceptor(EnCryptProperties properties) { this.rewriter = new EncryptPreParameterRewriter(properties); } @Override public Object intercept(Invocation invocation) throws Throwable { - - Object target = PluginUtils.realTarget(invocation.getTarget()); + Object target = realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(target); MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); @@ -49,4 +51,28 @@ public class EnCryptInterceptor implements Interceptor { return returnObj; } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } + + /** + *

+ * 获得真正的处理对象,可能多层代理. + *

+ */ + private Object realTarget(Object target) { + if (Proxy.isProxyClass(target.getClass())) { + MetaObject metaObject = SystemMetaObject.forObject(target); + return realTarget(metaObject.getValue("h.target")); + } + return target; + } + } diff --git a/src/main/java/org/dflish/mybatis/encrypt/rule/EncryptRule.java b/src/main/java/org/dflish/mybatis/encrypt/rule/EncryptRule.java index cee0031d197400507f4732cf03fbe2b4a939c3e0..9d404d387ee3b177b6c622b2bc5c499ee4b8a98b 100644 --- a/src/main/java/org/dflish/mybatis/encrypt/rule/EncryptRule.java +++ b/src/main/java/org/dflish/mybatis/encrypt/rule/EncryptRule.java @@ -1,15 +1,12 @@ package org.dflish.mybatis.encrypt.rule; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.symmetric.AES; -import org.apache.commons.codec.digest.DigestUtils; import org.dflish.mybatis.encrypt.properties.EnCryptProperties; import org.dflish.mybatis.encrypt.properties.EncryptColumnRuleConfiguration; import org.dflish.mybatis.encrypt.properties.EncryptTableRuleConfiguration; +import org.dflish.mybatis.encrypt.util.AesUtils; import org.dflish.mybatis.encrypt.util.DesensitizedExecutor; import org.dflish.mybatis.encrypt.util.DesensitizedType; -import java.util.Arrays; import java.util.Map; import java.util.Optional; @@ -24,16 +21,10 @@ public class EncryptRule { private final String key; - private final AES aes; - - public EncryptRule(EnCryptProperties properties) { this.properties = properties; this.tables = properties.getTables(); this.key = properties.getKey(); - - final byte[] bytes = Arrays.copyOf(DigestUtils.sha1(key), 16); - aes = SecureUtil.aes(bytes); } public Optional getEncryptColumnRuleConfiguration(String tableName, String columnName){ @@ -73,7 +64,7 @@ public class EncryptRule { final EncryptColumnRuleConfiguration configuration = encryptColumnRuleConfiguration.get(); if (configuration.getEncryptor()){ - return aes.encryptBase64((String) originalValues); + return AesUtils.encrypt((String) originalValues, key); } if (configuration.getDesensitized()){ diff --git a/src/main/java/org/dflish/mybatis/encrypt/util/AesUtils.java b/src/main/java/org/dflish/mybatis/encrypt/util/AesUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2d43c5cd0565eb06c4c99839e7eefea124e95ea9 --- /dev/null +++ b/src/main/java/org/dflish/mybatis/encrypt/util/AesUtils.java @@ -0,0 +1,90 @@ +package org.dflish.mybatis.encrypt.util; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import org.apache.commons.codec.binary.Hex; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/** + * aes工具类 + * + * @author song_jx + * @date 2021-09-04 05:14:04 + */ +public class AesUtils { + + private static final Log log = LogFactory.get(); + + /** + * 算法 + */ + private static final String ALGORITHM = "AES"; + + /** + * 生成mysql的aes密钥 + * + * @param key 密钥 + * @param encoding 编码 + * @return @return {@link SecretKeySpec } + * @author song_jx + */ + public static SecretKeySpec generateMySQLAESKey(final String key, final String encoding) throws Exception { + final byte[] finalKey = new byte[16]; + int i = 0; + for (byte b : key.getBytes(encoding)) { + finalKey[i++ % 16] ^= b; + } + return new SecretKeySpec(finalKey, ALGORITHM); + } + + /** + * 解密 + * + * @param data 数据 + * @param key 密钥 + * @return @return {@link String } + * @throws Exception 异常 + * @author song_jx + */ + public static String decrypt(String data, String key) { + String decryptStr = StrUtil.EMPTY; + try { + final Cipher decryptCipher = Cipher.getInstance(ALGORITHM); + decryptCipher.init(Cipher.DECRYPT_MODE, generateMySQLAESKey(key, CharsetUtil.UTF_8)); + decryptStr = new String(decryptCipher.doFinal(Hex.decodeHex(data.toCharArray()))); + } catch (Exception e) { + log.error("--- [AES工具类] aes解密失败...", e); + } + return decryptStr; + } + + /** + * 加密 + * + * @param data 数据 + * @param key 密钥 + * @return @return {@link String } + * @author song_jx + */ + public static String encrypt(String data, String key) { + String encryptStr = StrUtil.EMPTY; + try { + final Cipher encryptCipher = Cipher.getInstance(ALGORITHM); + encryptCipher.init(Cipher.ENCRYPT_MODE, generateMySQLAESKey(key, CharsetUtil.UTF_8)); + char[] code = Hex.encodeHex(encryptCipher.doFinal(data.getBytes(CharsetUtil.UTF_8))); + StringBuilder builder = new StringBuilder(); + for (char d : code) { + builder.append(d); + } + encryptStr = builder.toString(); + } catch (Exception e) { + log.error("--- [AES工具类] aes加密失败...", e); + } + return encryptStr; + } + +} \ No newline at end of file diff --git a/src/main/java/org/dflish/mybatis/encrypt/util/DecryptUtil.java b/src/main/java/org/dflish/mybatis/encrypt/util/DecryptUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..3b2afd76601509ed43b373a260f41c42b0f106e6 --- /dev/null +++ b/src/main/java/org/dflish/mybatis/encrypt/util/DecryptUtil.java @@ -0,0 +1,59 @@ +package org.dflish.mybatis.encrypt.util; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import org.dflish.mybatis.encrypt.annotation.DecryptTransaction; + +import java.lang.reflect.Field; + +/** + * 解密工具类 + * + * @author song_jx + * @date 2021-09-04 04:13:13 + */ +public class DecryptUtil { + + private static final Log log = LogFactory.get(); + + /** + * 解密 + * + * @param result 结果 + * @param key 关键 + * @return @return {@link T } + * @author song_jx + */ + public static T decrypt(T result, String key) { + try { + // 取出resultType的类 + Field[] declaredFields = ReflectUtil.getFields(result.getClass()); + for (Field field : declaredFields) { + // 取出所有被DecryptTransaction注解的字段 + DecryptTransaction annotation = AnnotationUtil.getAnnotation(field, DecryptTransaction.class); + if (ObjectUtil.isNotNull(annotation)) { + Object fieldValue = ReflectUtil.getFieldValue(result, field); + // String的解密 + if (fieldValue instanceof String) { + String value = (String) fieldValue; + value = AesUtils.decrypt(value, key); + + // 数据脱敏 + if (annotation.isDesensitized()) { + value = DesensitizedExecutor.desensitized(annotation.desensitizedType(), value); + } + + field.set(result, value); + } + } + } + } catch (Exception e) { + log.error("---> [Mybatis 解密拦截器] 字段注解 解密异常...", e); + } + return result; + } + +}