diff --git a/kernel-d-security/pom.xml b/kernel-d-security/pom.xml index 8e5942396..2b58463a9 100644 --- a/kernel-d-security/pom.xml +++ b/kernel-d-security/pom.xml @@ -23,6 +23,7 @@ security-sdk-count security-sdk-xss security-sdk-request-encrypt-and-decode + security-sdk-database-field security-spring-boot-starter diff --git a/kernel-d-security/security-api/src/main/java/cn/stylefeng/roses/kernel/security/api/expander/SecurityConfigExpander.java b/kernel-d-security/security-api/src/main/java/cn/stylefeng/roses/kernel/security/api/expander/SecurityConfigExpander.java index 3373a309f..10b3a669c 100644 --- a/kernel-d-security/security-api/src/main/java/cn/stylefeng/roses/kernel/security/api/expander/SecurityConfigExpander.java +++ b/kernel-d-security/security-api/src/main/java/cn/stylefeng/roses/kernel/security/api/expander/SecurityConfigExpander.java @@ -67,4 +67,15 @@ public class SecurityConfigExpander { } } + /** + * 获取AES秘钥 + * + * @return {@link String} + * @author majianguo + * @date 2021/7/5 10:15 + **/ + public static String getEncryptSecretKey() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_ENCRYPT_SECRET_KEY", String.class, "Ux1dqQ22KxVjSYootgzMe776em8vWEGE"); + } + } diff --git a/kernel-d-security/security-sdk-database-field/README.md b/kernel-d-security/security-sdk-database-field/README.md new file mode 100644 index 000000000..d9cb6f9e9 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/README.md @@ -0,0 +1,23 @@ +数据库字段加解密工具 + +注意: + +1.数据库加密字段的类型为String类型 + +2.如果开启功能时数据库对应字段已经有数据,需要对旧数据进行处理,可以通过EncryptAlgorithmApi对字段进行加密 + +说明: + +1.通过mybatis的拦截器,对传入参数和返回结果进行处理 + +2.可以满足对某个数据库字段在入库时加密,在查询时解密的需求 + +3.同时也满足对某个数据库字段在入库时加密,查询时依旧显示密文,只有在需要的时候解密的需求 + +使用方法: + +1.给类添加ProtectedData注解,以标识该类需要进行加解密处理 + +2.在需要加解密的字段上面添加ProtectedField注解,即可实现在入库时加密,查询时解密的功能 + +3.如果不想在查询时解密,则在字段上添加EncryptField注解替换ProtectedField注解,即可实现在入库时该字段加密,查询时不解密的功能 diff --git a/kernel-d-security/security-sdk-database-field/pom.xml b/kernel-d-security/security-sdk-database-field/pom.xml new file mode 100644 index 000000000..351d3ae4b --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + cn.stylefeng.roses + kernel-d-security + 7.0.4 + ../pom.xml + + + security-sdk-database-field + + jar + + + + + + cn.stylefeng.roses + security-api + ${roses.version} + + + + + cn.stylefeng.roses + scanner-api + ${roses.version} + + + + + org.springframework.boot + spring-boot-starter-web + + + + + cn.stylefeng.roses + db-api + ${roses.version} + + + + + \ No newline at end of file diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/EncryptAlgorithmApi.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/EncryptAlgorithmApi.java new file mode 100644 index 000000000..4633d9088 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/EncryptAlgorithmApi.java @@ -0,0 +1,32 @@ +package cn.stylefeng.roses.kernel.security.database.algorithm; + +/** + * 加密算法接口 + *

+ * 可根据自身需要自定义实现,默认实现为AES + * + * @author majianguo + * @date 2021/7/3 11:02 + */ +public interface EncryptAlgorithmApi { + + /** + * 加密算法 + * + * @param encryptedData 加密数据 + * @return {@link java.lang.String} + * @author majianguo + * @date 2021/7/3 11:07 + **/ + String encrypt(String encryptedData); + + /** + * 解密算法 + * + * @param cipher 待解密密文 + * @return {@link java.lang.String} + * @author majianguo + * @date 2021/7/3 11:33 + **/ + String decrypt(String cipher); +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/impl/AesEncryptAlgorithmApiImpl.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/impl/AesEncryptAlgorithmApiImpl.java new file mode 100644 index 000000000..ea034818f --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/algorithm/impl/AesEncryptAlgorithmApiImpl.java @@ -0,0 +1,34 @@ +package cn.stylefeng.roses.kernel.security.database.algorithm.impl; + +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; +import cn.hutool.crypto.symmetric.SymmetricCrypto; +import cn.stylefeng.roses.kernel.security.database.algorithm.EncryptAlgorithmApi; + +/** + * AES 加密解密实现 + * + * @author majianguo + * @date 2021/7/3 11:43 + */ +public class AesEncryptAlgorithmApiImpl implements EncryptAlgorithmApi { + + /** + * AES加密实体类 + */ + public final SymmetricCrypto symmetricCrypto; + + public AesEncryptAlgorithmApiImpl(byte[] key) { + symmetricCrypto = new SymmetricCrypto(SymmetricAlgorithm.AES, key); + } + + @Override + public String encrypt(String encryptedData) { + return symmetricCrypto.encryptHex(encryptedData); + } + + @Override + public String decrypt(String cipher) { + return symmetricCrypto.decryptStr(cipher); + } + +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/EncryptField.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/EncryptField.java new file mode 100644 index 000000000..f4a9fd306 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/EncryptField.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.security.database.annotation; + +import java.lang.annotation.*; + +/** + * 需要加密字段注解 + *

+ * 该注解作用范围在字段上面(该类需要加 {@link ProtectedData} 注解) + * + * @author majianguo + * @date 2021/7/3 10:57 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EncryptField { + +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedData.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedData.java new file mode 100644 index 000000000..fc018fbc3 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedData.java @@ -0,0 +1,18 @@ +package cn.stylefeng.roses.kernel.security.database.annotation; + +import java.lang.annotation.*; + +/** + * 被保护数据标识注解(标识那个DTO需要加解密) + *

+ * 该注解作用范围在类上面 + * + * @author majianguo + * @date 2021/7/3 10:54 + */ +@Inherited +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ProtectedData { + +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedField.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedField.java new file mode 100644 index 000000000..0e00c2401 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/annotation/ProtectedField.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.security.database.annotation; + +import java.lang.annotation.*; + +/** + * 需要加解密字段注解(该字段自动加解密,数据库是密文,查看时是明文) + *

+ * 该注解作用范围在字段上面(该类需要加 {@link ProtectedData} 注解) + * + * @author majianguo + * @date 2021/7/5 9:18 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ProtectedField { + +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ParameterInterceptor.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ParameterInterceptor.java new file mode 100644 index 000000000..3e7b9d36c --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ParameterInterceptor.java @@ -0,0 +1,90 @@ +package cn.stylefeng.roses.kernel.security.database.interceptor; + +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.security.database.algorithm.EncryptAlgorithmApi; +import cn.stylefeng.roses.kernel.security.database.annotation.EncryptField; +import cn.stylefeng.roses.kernel.security.database.annotation.ProtectedData; +import cn.stylefeng.roses.kernel.security.database.annotation.ProtectedField; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.plugin.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.util.Properties; + +/** + * Mybatis拦截器,拦截入库参数 + * + * @author majianguo + * @date 2021/7/3 11:58 + */ +@Slf4j +@Component +@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),}) +public class ParameterInterceptor implements Interceptor { + + @Autowired + private EncryptAlgorithmApi encryptAlgorithmApi; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + // 若指定ResultSetHandler ,这里则能强转为ResultSetHandler + ParameterHandler parameterHandler = (ParameterHandler)invocation.getTarget(); + + // 获取参数对像,即 mapper 中 paramsType 的实例 + Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); + parameterField.setAccessible(true); + + // 取出实例 + Object parameterObject = parameterField.get(parameterHandler); + if (parameterObject != null) { + Class parameterObjectClass = parameterObject.getClass(); + + // 校验该实例的类是否被@ProtectedData所注解 + ProtectedData protectedData = AnnotationUtils.findAnnotation(parameterObjectClass, ProtectedData.class); + if (ObjectUtil.isNotNull(protectedData)) { + + //取出当前当前类所有字段 + Field[] declaredFields = parameterObjectClass.getDeclaredFields(); + + // 处理需要加密的字段 + for (Field declaredField : declaredFields) { + + // 包含其中任意一个即可 + ProtectedField protectedField = declaredField.getAnnotation(ProtectedField.class); + EncryptField encryptField = declaredField.getAnnotation(EncryptField.class); + if (ObjectUtil.isNotNull(protectedField) || ObjectUtil.isNotNull(encryptField)) { + declaredField.setAccessible(true); + Object fieldData = declaredField.get(parameterObject); + // 如果是String就处理 + if (fieldData instanceof String) { + String value = (String)fieldData; + try { + String encrypt = encryptAlgorithmApi.encrypt(value); + declaredField.set(parameterObject, encrypt); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + } + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } +} diff --git a/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ResultInterceptor.java b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ResultInterceptor.java new file mode 100644 index 000000000..661e9fd35 --- /dev/null +++ b/kernel-d-security/security-sdk-database-field/src/main/java/cn/stylefeng/roses/kernel/security/database/interceptor/ResultInterceptor.java @@ -0,0 +1,135 @@ +package cn.stylefeng.roses.kernel.security.database.interceptor; + +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.security.database.algorithm.EncryptAlgorithmApi; +import cn.stylefeng.roses.kernel.security.database.annotation.ProtectedData; +import cn.stylefeng.roses.kernel.security.database.annotation.ProtectedField; +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 java.lang.reflect.Field; +import java.sql.Statement; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + +/** + * Mybatis拦截器,拦截返回参数 + * + * @author majianguo + * @date 2021/7/3 11:58 + */ +@Slf4j +@Component +@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) +public class ResultInterceptor implements Interceptor { + + @Autowired + private EncryptAlgorithmApi encryptAlgorithmApi; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + //取出查询的结果 + Object resultObject = invocation.proceed(); + if (Objects.isNull(resultObject)) { + return null; + } + + // 判断结果是List还是对象 + if (resultObject instanceof List) { + List resultList = (List)resultObject; + // 判断是否为空 + if (ObjectUtil.isNotNull(resultList)) { + // 处理数据 + for (Object result : resultList) { + // 对象处理 + this.objectProcessing(result); + } + } + } else { + // 处理单个对象 + this.objectProcessing(resultObject); + } + + return resultObject; + } + + /** + * 对象处理 + * + * @return + * @author majianguo + * @date 2021/7/5 9:52 + **/ + private void objectProcessing(Object result) throws IllegalAccessException { + Class resultClass = result.getClass(); + // 是否加注解了 + ProtectedData annotation = AnnotationUtils.findAnnotation(resultClass, ProtectedData.class); + + // 加注解就去处理 + if (ObjectUtil.isNotNull(annotation)) { + Field[] declaredFields = resultClass.getDeclaredFields(); + for (Field field : declaredFields) { + // 处理字段 + this.fieldProcessing(result, field); + } + } + } + + /** + * @param result + * @param field + * @return + * @author majianguo + * @date 2021/7/5 9:52 + **/ + private void fieldProcessing(Object result, Field field) throws IllegalAccessException { + if (this.isTag(field)) { + field.setAccessible(true); + Object object = field.get(result); + //String的解密 + if (object instanceof String) { + String value = (String)object; + //对注解的字段进行逐一解密 + try { + String decrypt = encryptAlgorithmApi.decrypt(value); + field.set(result, decrypt); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + /** + * 是否注解标记了 + * + * @param field 被判断字段 + * @return {@link boolean} + * @author majianguo + * @date 2021/7/5 9:35 + **/ + private boolean isTag(Field field) { + // 包含其中任意一个即可 + ProtectedField protectedField = field.getAnnotation(ProtectedField.class); + if (ObjectUtil.isNotNull(protectedField)) { + return true; + } + return false; + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } +} diff --git a/kernel-d-security/security-spring-boot-starter/pom.xml b/kernel-d-security/security-spring-boot-starter/pom.xml index 02a88e64c..f8a469d5a 100644 --- a/kernel-d-security/security-spring-boot-starter/pom.xml +++ b/kernel-d-security/security-spring-boot-starter/pom.xml @@ -52,6 +52,13 @@ ${roses.version} + + + cn.stylefeng.roses + security-sdk-database-field + ${roses.version} + + cn.stylefeng.roses diff --git a/kernel-d-security/security-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/security/starter/EncryptAlgorithmAutoConfiguration.java b/kernel-d-security/security-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/security/starter/EncryptAlgorithmAutoConfiguration.java new file mode 100644 index 000000000..7f779959a --- /dev/null +++ b/kernel-d-security/security-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/security/starter/EncryptAlgorithmAutoConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright [2020-2030] [https://www.stylefeng.cn] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Guns源码头部的版权声明。 + * 3.请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns + * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns + * 6.若您的项目无法满足以上几点,可申请商业授权 + */ +package cn.stylefeng.roses.kernel.security.starter; + +import cn.stylefeng.roses.kernel.security.api.expander.SecurityConfigExpander; +import cn.stylefeng.roses.kernel.security.database.algorithm.EncryptAlgorithmApi; +import cn.stylefeng.roses.kernel.security.database.algorithm.impl.AesEncryptAlgorithmApiImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.nio.charset.StandardCharsets; + +/** + * 加密算法自动配置 + * + * @author majianguo + * @date 2021/7/5 10:06 + */ +@Configuration +public class EncryptAlgorithmAutoConfiguration { + + /** + * 数据库加密算法 + * + * @return {@link cn.stylefeng.roses.kernel.security.database.algorithm.EncryptAlgorithmApi} + * @author majianguo + * @date 2021/7/5 10:16 + **/ + @Bean + @ConditionalOnMissingBean(EncryptAlgorithmApi.class) + public EncryptAlgorithmApi encryptAlgorithmApi() { + String encryptSecretKey = SecurityConfigExpander.getEncryptSecretKey(); + return new AesEncryptAlgorithmApiImpl(encryptSecretKey.getBytes(StandardCharsets.UTF_8)); + } + +}