问题
在项目中需要对用户敏感数据进行脱敏处理,例如身份号、手机号等信息进行加密再入库。
解决思路
就是:一种最简单直接的方式,在所有涉及数据敏感的查询到对插入时进行密码加解密
方法二:有方法一到出现对所有重大问题的影响,需要考虑到问题的出现,并且需要考虑可能出现的组员时添加数据的方法。
最后决定采用mybatis的插件在mybatis的SQL执行和结果填充操作上进行切入。上层业务调用不再需要考虑数据的加敏同时也保证了数据的加解密
Mybatis 插件原理
Mybatis 的是通过拦截器实现的,Mabatis 支持对当事人进行拦截
实现
设置对参数中带有敏感参数字段的数据时进行加密
对返回的结果进行解密处理
根据不同的要求,我们只需要对ParameterHandler和ResultSetHandler进行切入。定义特定注解,在切入时需要检查字段中是否包含注解来是否加解密。
加注解
定义SensitiveData注解
import java.lang.annotation.*; /** * 该注解定义在类上 * 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解 * 这个注解要配合EncryptTransaction注解 **/ @Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface SensitiveData { }
定义EncryptTransaction注解
import java.lang.annotation.*; /** * 该注解有两种使用方式 * ①:配合@SensitiveData加在类中的字段上 * ②:直接在Mapper中的方法参数上使用 **/ @Documented @Inherited @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface EncryptTransaction { }
加解密工具类
解密
package sicnu.cs.ich.common.interceptor.transaction.service; public interface IDecryptUtil { /** * 解密 * * @param result resultType的实例 * @return T * @throws IllegalAccessException 字段不可访问异常 */
加密接口
package sicnu.cs.ich.common.interceptor.transaction.service; import java.lang.reflect.Field; public interface IEncryptUtil { /** * 加密 * * @param declaredFields 加密字段 * @param paramsObject 对象 * @param
加密实现类
package sicnu.cs.ich.common.interceptor.transaction.service.impl; import com.fasterxml.jackson.databind.ObjectReader; import org.springframework.stereotype.Component; import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction; import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil; import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil; import java.io.ObjectInputStream; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Objects; import java.util.Random; @Component public class EncryptUtilImpl implements IEncryptUtil { @Override public
模拟类
package sicnu.cs.ich.common.interceptor.transaction.service.impl; import org.springframework.stereotype.Component; import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction; import sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil; import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil; import java.lang.reflect.Field; import java.util.Objects; @Component public class DecryptImpl implements IDecryptUtil { /** * 解密 * * @param result resultType的实例 */ @Override public
加解密工具类
package sicnu.cs.ich.common.util.keyCryptor; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class DBAESUtil { private static final String DEFAULT_V = "6859505890402435"; // 自己填写 private static final String KEY = "***"; private static final String ALGORITHM = "AES"; private static SecretKeySpec getKey() { byte[] arrBTmp = DBAESUtil.KEY.getBytes(); // 创建一个空的16位字节数组(默认值为0) byte[] arrB = new byte[16]; for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) { arrB[i] = arrBTmp[i]; } return new SecretKeySpec(arrB, ALGORITHM); } /** * 加密 */ public static String encrypt(String content) throws Exception { final Base64.Encoder encoder = Base64.getEncoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); byte[] encrypted = cipher.doFinal(content.getBytes()); return encoder.encodeToString(encrypted); } /** * 解密 */ public static String decrypt(String content) throws Exception { final Base64.Decoder decoder = Base64.getDecoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); byte[] base64 = decoder.decode(content); byte[] original = cipher.doFinal(base64); return new String(original); } }
推荐一个开源免费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
插件实现
参数插件ParameterInterceptor
切入mybatis设置参数时对敏感数据进行加密
Mybatis插件的使用就是通过实现Mybatis中的Interceptor接口
再@Intercepts注解
// 使用mybatis插件时需要定义签名 // type标识需要切入的Handler // method表示要要切入的方法 @Intercepts({ @Signature(type = ParameterHandler.class, method = “setParameters”, args = PreparedStatement.class), }) package sicnu.cs.ich.common.interceptor.transaction; import com.baomidou.mybatisplus.core.MybatisParameterHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction; import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData; import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil; import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.sql.PreparedStatement; import java.util.*; @Slf4j // 注入Spring @Component @Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class), }) public class ParameterInterceptor implements Interceptor { @Autowired private IEncryptUtil IEncryptUtil; @Override public Object intercept(Invocation invocation) throws Throwable { //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler //若指定ResultSetHandler ,这里则能强转为ResultSetHandler MybatisParameterHandler parameterHandler = (MybatisParameterHandler) invocation.getTarget(); // 获取参数对像,即 mapper 中 paramsType 的实例 Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); parameterField.setAccessible(true); //取出实例 Object parameterObject = parameterField.get(parameterHandler); // 搜索该方法中是否有需要加密的普通字段 List
返回值插件ResultSetInterceptor
package sicnu.cs.ich.common.interceptor.transaction; 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 org.springframework.util.CollectionUtils; import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData; import java.sql.Statement; import java.util.ArrayList; import java.util.Objects; import java.util.Properties; @Slf4j @Component @Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) public class ResultSetInterceptor implements Interceptor { @Autowired private sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil IDecryptUtil; @Override public Object intercept(Invocation invocation) throws Throwable { //取出查询的结果 Object resultObject = invocation.proceed(); if (Objects.isNull(resultObject)) { return null; } //基于selectList if (resultObject instanceof ArrayList) { @SuppressWarnings("unchecked") ArrayList
使用
注意解在实体类上
import lombok.*; import org.springframework.security.core.userdetails.UserDetails; import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction; import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData; @With @Builder @Data @NoArgsConstructor @AllArgsConstructor @SensitiveData // 插件只对加了该注解的类进行扫描,只有加了这个注解的类才会生效 public class User implements Serializable { private Integer id; private String username; private String openId; private String password; // 表明对该字段进行加密 @EncryptTransaction private String email; // 表明对该字段进行加密 @EncryptTransaction private String mobile; private Date createTime; private Date expireTime; private Boolean status = true; }
注解在参数上
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction; @Mapper public interface UserMapper extends BaseMapper