自定义注解+拦截器实现,对部分敏感字段的加解密

上一篇,我用的自定义注解+AOP的方式,实现敏感字段的加解密,这一篇换个方案,这个方案相比一个方案,节省了一部分的性能开销

第一步:新建自定义注解

/**
 * 敏感信息类注解
 */
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableSensitive {
    boolean value() default true;
}
/**
 * 敏感字段注解
 */
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveFiled {
    String value() default "";
}

第二步:创建加密实现类

@Component
@Intercepts({
        //@Signature注解定义拦截器的实际类型
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {

    @Resource
    private SensitiveInfoService sensitiveInfoService;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 取出请求参数
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        Object parameterObject = parameterHandler.getParameterObject();
        // 如果参数不为空,并且是MapperMethod.ParamMap类型
        if (parameterObject instanceof MapperMethod.ParamMap) {
            Map<String, ?> paramMap = (MapperMethod.ParamMap) parameterObject;
            // 遍历请求参数,对敏感信息字段加密
            paramMap.forEach((k, v) -> {
                // 参数不为空,并且不是mybatis默认参数名
                if (null != paramMap.get(k)) {
                    this.encrypt(paramMap.get(k));
                }
            });
        } else if (null != parameterObject) {
            this.encrypt(parameterObject);
        }
        return invocation.proceed();
    }

    private void encrypt(Object param) {
        EnableSensitive enableSensitive = AnnotationUtils.findAnnotation(param.getClass(), EnableSensitive.class);
        if (null != enableSensitive && enableSensitive.value()) {
            // 对敏感信息类中的敏感信息字段加密
            sensitiveInfoService.encrypt(param);
        }
    }

    /**
     * 加入此拦截器到拦截器链
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

代码注释:
这段代码的功能是实现一个名为EncryptInterceptor的类,用作MyBatis的拦截器,主要对数据库操作中的敏感信息字段进行加密处理。具体功能如下:

  1. 依赖注入:通过@Resource注解,注入SensitiveInfoService服务,用于敏感信息的加密处理。
  2. 拦截器实现:实现Interceptor接口,对指定的方法进行拦截。此处拦截的是ParameterHandler类的setParameters方法,该方法负责设置PreparedStatement的参数。
  3. 参数获取与判断:在intercept方法中,获取ParameterHandler的参数对象。如果参数对象不为空且为MapperMethod.ParamMap类型(即Mapper接口方法的参数被封装成Map形式),则进行后续处理。
  4. 获取ParameterHandler的参数对象如果不是MapperMethod.ParamMap类型,则对整个对象加密
  5. 敏感信息加密:遍历参数Map,对每个非空参数值,,encrypt方法用于判断参数类上是否有@EnableSensitive注解,若有则调用sensitiveInfoService进行加密处理。
  6. 执行流程控制:完成加密处理后,通过invocation.proceed()继续执行被拦截的方法,即ParameterHandler的setParameters方法。
  7. 插件机制:通过plugin方法,利用MyBatis的插件机制,将此拦截器加入到MyBatis的拦截器链中,从而对指定的目标对象进行增强处理。
    综上所述,EncryptInterceptor的主要作用是在MyBatis执行SQL之前,拦截并加密Mapper方法中的敏感信息字段,确保敏感数据的安全性。

第三步:创建解密实现类

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

    @Resource
    private SensitiveInfoService sensitiveInfoService;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (null != resultObject) {
            // 校验是否需要解密,并对敏感信息进行解密
            sensitiveInfoService.decrypt(resultObject);
        }
        return resultObject;
    }

    /**
     * 加入此拦截器到拦截器链
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

代码注释:
这段代码定义了一个名为DecryptInterceptor的拦截器类,用于在查询数据库后对结果进行解密。该拦截器通过@Component和@Intercepts注解标记,指定了它将拦截ResultSetHandler类中的handleResultSets方法,该方法接受一个Statement类参数。
主要功能包括:

  1. 拦截查询结果:在intercept方法中,拦截器取出查询的结果对象。
  2. 解密敏感信息:利用注入的SensitiveInfoService服务,检查查询结果是否需要解密,并对包含的敏感信息执行解密操作。
  3. 返回解密后的结果:最后,将处理后的结果返回。

此外,plugin方法用于将此拦截器加入到拦截器链中,使用Plugin.wrap方法包装目标对象。

具体的加解密service

public interface SensitiveInfoService {

    /**
     * 加密
     *
     * @param param 待加密字段外层对象
     */
    <T> T encrypt(T param);

    /**
     * 解密
     *
     * @param result 待解密字段外层对象
     */
    <T> T decrypt(T result);
}

加解密impl

@Service
public class SensitiveInfoServiceImpl implements SensitiveInfoService {
    public static final Logger log = LoggerFactory.getLogger(SensitiveInfoServiceImpl.class);

    /**
     * 密钥
     */
    private static final String SM4_KEY = "your_key";

    /**
     * 加密过标识
     */
    public static final String ENCRYPTED_PREFIX = "{SSI}";

    @Override
    public <T> T encrypt(T param) {
        // 校验是否需要加密,并返回需要加密字段
        List<Field> fields = this.checkAndGetFields(param);
        // 加密字段不为空,加密
        if (!CollectionUtils.isEmpty(fields)) {
            fields.forEach(field -> {
                // 加密
                this.encrypt(field, param);
            });
        }
        return param;
    }

    /**
     * 加密
     */
    private void encrypt(Field field, Object o) {
        // 如果是集合,则遍历出实体,逐个加密
        if (o instanceof Collection) {
            ((Collection<?>) o).forEach(e -> {
                this.encryptObj(field, e);
            });
        } else {
            this.encryptObj(field, o);
        }
    }

    /**
     * 加密
     */
    private void encryptObj(Field field, Object o) {
        try {
            // 访问private变量的变量值
            field.setAccessible(true);
            // 字段值
            Object v = field.get(o);
            // 只对String类型来加密
            if (v instanceof String) {
                String value = (String) v;
                // 如果没加密过 (不以{SSI}开头,或者没加密)
                if (StringUtils.isNotEmpty(value) && null == this.decryptStr(value)) {
                    // 加密并重新赋值
                    field.set(o, ENCRYPTED_PREFIX + SM4Util.encryptEcb(SM4_KEY, value));
                }
            }
        } catch (Exception e) {
            log.error("字段加密失败,类={}, 字段={}", o.getClass().getName(), field.getName(), e);
        }
    }

    @Override
    public <T> T decrypt(T result) {
        // 校验是否需要解密,并返回需要解密字段
        List<Field> fields = this.checkAndGetFields(result);
        // 解密字段不为空,解密
        if (!CollectionUtils.isEmpty(fields)) {
            fields.forEach(field -> {
                // 解密
                this.decrypt(field, result);
            });
        }
        return result;
    }

    /**
     * 解密
     */
    private void decrypt(Field field, Object o) {
        // 如果是集合,则遍历出实体,逐个解密
        if (o instanceof Collection) {
            ((Collection<?>) o).forEach(e -> {
                this.decryptObj(field, e);
            });
        } else {
            this.decryptObj(field, o);
        }
    }

    /**
     * 对象中字段解密
     */
    private void decryptObj(Field field, Object o) {
        try {
            // 访问private变量的变量值
            field.setAccessible(true);
            // 字段值
            Object value = field.get(o);
            // 只对String类型来解密
            if (value instanceof String) {
                // 解密
                String decrypt = this.decryptStr((String) value);
                // 解密成功,重新赋值
                if(StringUtils.isNotEmpty(decrypt)) {
                    field.set(o, decrypt);
                }
            }
        } catch (Exception e) {
            log.error("字段解密失败,类={}, 字段={}", o.getClass().getName(), field.getName(), e);
        }
    }


    /**
     * 加密信息解密
     */
    private String decryptStr(String value) {
        if (StringUtils.isNotEmpty(value) && value.startsWith(ENCRYPTED_PREFIX)) {
            try {
                return SM4Util.decryptEcb(SM4_KEY, value.substring(ENCRYPTED_PREFIX.length()));
            } catch (Exception e) {
                log.error("字段解密失败,value={}", value, e);
            }
        }
        return null;
    }

    /**
     * 校验是否需要加解密,并返回需要加解密字段
     */
    private <T> List<Field> checkAndGetFields(T t) {
        // 数据为空直接返回
        if (null == t) {
            return null;
        }
        if (t instanceof Collection) {
            // 如果是集合,返回集合中元素的类中需要加解密的敏感信息字段
            Collection<?> rc = (Collection<?>) t;
            if (!CollectionUtils.isEmpty(rc)) {
                Object next = rc.iterator().next();
                if (null != next) {
                    return ReflectUtils.getAllField(next.getClass(), SensitiveFiled.class);
                }
            }
        } else {
            // 返回需要加解密的敏感信息字段
            return ReflectUtils.getAllField(t.getClass(), SensitiveFiled.class);
        }
        return null;
    }
}

示例:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值