新增数据库字段加解密模块

pull/22/head
rays 2021-07-05 14:16:11 +08:00
parent 485a01cdec
commit 25fab57768
13 changed files with 496 additions and 0 deletions

View File

@ -23,6 +23,7 @@
<module>security-sdk-count</module> <module>security-sdk-count</module>
<module>security-sdk-xss</module> <module>security-sdk-xss</module>
<module>security-sdk-request-encrypt-and-decode</module> <module>security-sdk-request-encrypt-and-decode</module>
<module>security-sdk-database-field</module>
<module>security-spring-boot-starter</module> <module>security-spring-boot-starter</module>
</modules> </modules>

View File

@ -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");
}
} }

View File

@ -0,0 +1,23 @@
数据库字段加解密工具
注意:
1.数据库加密字段的类型为String类型
2.如果开启功能时数据库对应字段已经有数据需要对旧数据进行处理可以通过EncryptAlgorithmApi对字段进行加密
说明:
1.通过mybatis的拦截器对传入参数和返回结果进行处理
2.可以满足对某个数据库字段在入库时加密,在查询时解密的需求
3.同时也满足对某个数据库字段在入库时加密,查询时依旧显示密文,只有在需要的时候解密的需求
使用方法:
1.给类添加ProtectedData注解以标识该类需要进行加解密处理
2.在需要加解密的字段上面添加ProtectedField注解即可实现在入库时加密查询时解密的功能
3.如果不想在查询时解密则在字段上添加EncryptField注解替换ProtectedField注解即可实现在入库时该字段加密查询时不解密的功能

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>kernel-d-security</artifactId>
<version>7.0.4</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>security-sdk-database-field</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--安全模块的api-->
<dependency>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>security-api</artifactId>
<version>${roses.version}</version>
</dependency>
<!--scanner api-->
<dependency>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>scanner-api</artifactId>
<version>${roses.version}</version>
</dependency>
<!--web模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库的依赖,主要依赖mybatis-->
<dependency>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>db-api</artifactId>
<version>${roses.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package cn.stylefeng.roses.kernel.security.database.algorithm;
/**
*
* <p>
* 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);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,19 @@
package cn.stylefeng.roses.kernel.security.database.annotation;
import java.lang.annotation.*;
/**
*
* <p>
* ( {@link ProtectedData} )
*
* @author majianguo
* @date 2021/7/3 10:57
*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}

View File

@ -0,0 +1,18 @@
package cn.stylefeng.roses.kernel.security.database.annotation;
import java.lang.annotation.*;
/**
* (DTO)
* <p>
*
*
* @author majianguo
* @date 2021/7/3 10:54
*/
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtectedData {
}

View File

@ -0,0 +1,19 @@
package cn.stylefeng.roses.kernel.security.database.annotation;
import java.lang.annotation.*;
/**
*
* <p>
* ( {@link ProtectedData} )
*
* @author majianguo
* @date 2021/7/5 9:18
*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtectedField {
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -52,6 +52,13 @@
<version>${roses.version}</version> <version>${roses.version}</version>
</dependency> </dependency>
<!--数据库加密解密模块-->
<dependency>
<groupId>cn.stylefeng.roses</groupId>
<artifactId>security-sdk-database-field</artifactId>
<version>${roses.version}</version>
</dependency>
<!--CORS过滤器默认不开启--> <!--CORS过滤器默认不开启-->
<dependency> <dependency>
<groupId>cn.stylefeng.roses</groupId> <groupId>cn.stylefeng.roses</groupId>

View File

@ -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.
*
* GunsAPACHE 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));
}
}