springboot+mybatis+自定义注解实现对数据库中的字段进行加解密

1.创建自定义注解类,该注解标注在类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveData {

}

2,创建自定义字段注解,该注解标注在字段上

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveField {

}

3.引入项目需要的依赖

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
    </dependency>
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.11.1</version>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.2</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--aes 使用Base64加密时需要用到-->
    <dependency>
      <groupId>org.apache.directory.studio</groupId>
      <artifactId>org.apache.commons.codec</artifactId>
      <version>1.8</version>
    </dependency>
  </dependencies>

4.yml配置文件

server:
  port: 8890
spring:
  application:
    name: quartz-demo
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false&ServerTimeZone=Asia/ShangHai
  #batch: 注释掉与batch相关的配置
    #jdbc:
     #initialize-schema: always
    #job:
      #enabled: true
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.quartz.demo.domain
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
aes:
  key: sddwllwwasereddsssdfefdffeerwwsff

5.创建加密接口,之所以创建接口是为了后面加密的拓展,该文章中涉及到的加解密都是使用AES进行加解密

public interface EncryptService {
  /**
   * 加密
   *
   * @param declaredFields paramsObject所声明的字段
   * @param paramsObject   mapper中paramsType的实例
   * @return T
   * @throws IllegalAccessException 字段不可访问异常
   */
  <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception;
}

6.加密接口实现了类

import com.quartz.demo.annotation.SensitiveField;
import com.quartz.demo.service.EncryptService;
import com.quartz.demo.utils.AESUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Objects;

@Component
public class AesEncryptServiceImpl implements EncryptService {

  private AESUtils aesUtils;

  @Value("${aes.key}")
  private String key;

  @Autowired
  public AesEncryptServiceImpl(AESUtils aesUtils) {
    this.aesUtils = aesUtils;
  }

  @Override
  public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
    for (Field field : declaredFields) {
      //取出所有被EncryptDecryptField注解的字段
      SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
      if (!Objects.isNull(sensitiveField)) {
        field.setAccessible(true);
        Object object = field.get(paramsObject);
        //暂时只实现String类型的加密
        if (object instanceof String) {
          String value = (String) object;
          //加密  这里我使用自定义的AES加密工具
          field.set(paramsObject, aesUtils.aesEncrypt(value,key));
        }
      }
    }
    return paramsObject;
  }
}

7.解密接口

public interface DecryptService {
  /**
   * 解密
   *
   * @param result resultType的实例
   * @return T
   * @throws IllegalAccessException 字段不可访问异常
   */
  <T> T decrypt(T result) throws Exception;
}

7.解密接口实现类

import com.quartz.demo.annotation.SensitiveField;
import com.quartz.demo.service.DecryptService;
import com.quartz.demo.utils.AESUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Objects;

@Component
public class AESDecryptServiceImpl implements DecryptService {

  private AESUtils aesUtils;

  @Value("${aes.key}")
  private String key;

  @Autowired
  public AESDecryptServiceImpl(AESUtils aesUtils) {
    this.aesUtils = aesUtils;
  }

  @Override
  public <T> T decrypt(T result) throws Exception {
    //取出resultType的类
    Class<?> resultClass = result.getClass();
    Field[] declaredFields = resultClass.getDeclaredFields();
    for (Field field : declaredFields) {
      //取出所有被EncryptDecryptField注解的字段
      SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
      if (!Objects.isNull(sensitiveField)) {
        field.setAccessible(true);
        Object object = field.get(result);
        //只支持String的解密
        if (object instanceof String) {
          String value = (String) object;
          //对注解的字段进行逐一解密
          field.set(result, aesUtils.aesDecrypt(value,key));
        }
      }
    }
    return result;
  }
}

8.AES加解密工具类

import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

/**
 * 功能:AES 工具类
 * 说明:
 * @author Mr.Zheng
 * @date 2020-5-20 11:25
 */
@Component
public class AESUtils {
  public  String aesDecrypt(String encryptStr, String secretKey) throws Exception {
    byte[] decodeBytes = new BASE64Decoder().decodeBuffer(encryptStr);
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    random.setSeed(secretKey.getBytes());
    keyGenerator.init(128, random);
    Cipher cipher = Cipher.getInstance("AES");

    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyGenerator.generateKey().getEncoded(), "AES"));
    byte[] decryptBytes = cipher.doFinal(decodeBytes);

    return new String(decryptBytes);
  }

  public String aesEncrypt(String originStr, String secretKey) throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    random.setSeed(secretKey.getBytes());
    keyGenerator.init(128, random);
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyGenerator.generateKey().getEncoded(), "AES"));
    byte[] encodeBytes = cipher.doFinal(originStr.getBytes("utf-8"));
    String encryptStr = new BASE64Encoder().encode(encodeBytes);
    return encryptStr;
  }
}

9.对于数据的加密与解密,应当存在两个拦截器对数据进行拦截操作参照官方文档,因此此处我们应当使用ParameterHandler拦截器对入参进行加密使用ResultSetHandler拦截器对出参进行解密操作,因此我们需要定义拦截器来处理数据,mybatis的interceptor接口有以下方法需要实现

public interface Interceptor {
 
  //主要参数拦截方法
  Object intercept(Invocation invocation) throws Throwable;
 
  //mybatis插件链
  default Object plugin(Object target) {return Plugin.wrap(target, this);}
 
  //自定义插件配置文件方法
  default void setProperties(Properties properties) {}
 
}

10.定义入参处理拦截器 

import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.service.EncryptService;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.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.Objects;
import java.util.Properties;

@Component
@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {

  private EncryptService encryptService;

  @Autowired
  public EncryptInterceptor(EncryptService encryptService) {
    this.encryptService = encryptService;
  }

  public Object intercept(Invocation invocation) throws Throwable {
    //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
    //若指定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();
      //校验该实例的类是否被@SensitiveData所注解
      SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
      if (Objects.nonNull(sensitiveData)) {
        //取出当前当前类所有字段,传入加密方法
        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        encryptService.encrypt(declaredFields, parameterObject);
      }
    }
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Interceptor.super.plugin(target);
  }

  @Override
  public void setProperties(Properties properties) {
    Interceptor.super.setProperties(properties);
  }
}

11.定义出参(mybatis查询返回的结果)拦截器

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.service.DecryptService;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;

@Component
@Intercepts(
    @Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
)
public class DecryptInterceptor implements Interceptor {

  private DecryptService decryptService;

  @Autowired
  public DecryptInterceptor(DecryptService decryptService) {
    this.decryptService = decryptService;
  }

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    //取出查询的结果
    Object resultObject = invocation.proceed();
    if (Objects.isNull(resultObject)) {
      return null;
    }
    //基于selectList
    if (resultObject instanceof ArrayList) {
      ArrayList resultList = (ArrayList) resultObject;
      if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
        for (Object result : resultList) {
          //逐一解密
          decryptService.decrypt(result);
        }
      }
      //基于selectOne
    } else {
      if (needToDecrypt(resultObject)) {
        decryptService.decrypt(resultObject);
      }
    }
    return resultObject;
  }

  private boolean needToDecrypt(Object object) {
    Class<?> objectClass = object.getClass();
    SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
    return Objects.nonNull(sensitiveData);
  }
  @Override
  public Object plugin(Object target) {
    return Interceptor.super.plugin(target);
  }

  @Override
  public void setProperties(Properties properties) {
    Interceptor.super.setProperties(properties);
  }
}

 12.拦截器参数讲解

@Intercepts 注解开启拦截器,@Signature 注解定义拦截器的实际类型。

@Signature中

  • type 属性指定当前拦截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一种

  • method 属性指定使用以上四种类型的具体方法(可进入class内部查看其方法)。

  • args 属性指定预编译语句

  

 13.使用自定义注解注解需要加密的字段

import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.annotation.SensitiveField;

@SensitiveData
public class User {
  private Long id;
  private String userName;
  @SensitiveField
  private String password;
  @SensitiveField
  private String telephone;

  public User() {
  }

  public User(Long id, String userName, String password, String telephone) {
    this.id = id;
    this.userName = userName;
    this.password = password;
    this.telephone = telephone;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getTelephone() {
    return telephone;
  }

  public void setTelephone(String telephone) {
    this.telephone = telephone;
  }

  @Override
  public String toString() {
    return "User{" +
        "id=" + id +
        ", userName='" + userName + '\'' +
        ", password='" + password + '\'' +
        ", telephone='" + telephone + '\'' +
        '}';
  }
}

13.测试验证加密效果,因为数据最终是保存在数据库里面的,所以只需要看新增后User中的加密字段有没有加密即可

 

14.解密后的查询结果,加密数据可以显示解密后的数据即可

 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值