mirror of https://gitee.com/stylefeng/roses
新增数据库字段加解密模块
parent
485a01cdec
commit
25fab57768
|
@ -23,6 +23,7 @@
|
|||
<module>security-sdk-count</module>
|
||||
<module>security-sdk-xss</module>
|
||||
<module>security-sdk-request-encrypt-and-decode</module>
|
||||
<module>security-sdk-database-field</module>
|
||||
<module>security-spring-boot-starter</module>
|
||||
</modules>
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
数据库字段加解密工具
|
||||
|
||||
注意:
|
||||
|
||||
1.数据库加密字段的类型为String类型
|
||||
|
||||
2.如果开启功能时数据库对应字段已经有数据,需要对旧数据进行处理,可以通过EncryptAlgorithmApi对字段进行加密
|
||||
|
||||
说明:
|
||||
|
||||
1.通过mybatis的拦截器,对传入参数和返回结果进行处理
|
||||
|
||||
2.可以满足对某个数据库字段在入库时加密,在查询时解密的需求
|
||||
|
||||
3.同时也满足对某个数据库字段在入库时加密,查询时依旧显示密文,只有在需要的时候解密的需求
|
||||
|
||||
使用方法:
|
||||
|
||||
1.给类添加ProtectedData注解,以标识该类需要进行加解密处理
|
||||
|
||||
2.在需要加解密的字段上面添加ProtectedField注解,即可实现在入库时加密,查询时解密的功能
|
||||
|
||||
3.如果不想在查询时解密,则在字段上添加EncryptField注解替换ProtectedField注解,即可实现在入库时该字段加密,查询时不解密的功能
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -52,6 +52,13 @@
|
|||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--数据库加密解密模块-->
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
<artifactId>security-sdk-database-field</artifactId>
|
||||
<version>${roses.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--CORS过滤器,默认不开启-->
|
||||
<dependency>
|
||||
<groupId>cn.stylefeng.roses</groupId>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue