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

上一篇,我用的自定义注解+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;
    }
}

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

在这里插入图片描述

<think>嗯,用户想了解如何使用MyBatis-Plus 3.4的拦截器实现敏感数据加密。我之前处理过类似的问题,记得MyBatis-Plus的拦截器可以用来在SQL执行前后进行自定义处理。首先,我需要确认用户提到的MyBatis-Plus版本是3.4,这可能涉及到特定的API,比如MetaObjectHandler或者自定义的TypeHandler。 根据引用[1],里面提到了自定义TypeHandler来实现加密解密逻辑,同时在配置中注册它,并在实体类中使用。这可能是一个关键点。不过用户提到的是拦截器,而TypeHandler通常用于类型转换,可能在数据写入和读取时处理加密解密。而拦截器比如InnerInterceptor可能用于更底层的SQL处理,比如分页或者乐观锁,如引用[5]中的例子。 但用户的问题明确提到“拦截器”,所以可能需要结合两种方法。例如,在插入或更新时,使用拦截器来拦截参数,并对其中的敏感字段进行加密。或者,可能需要在TypeHandler中处理加密,而拦截器用于某些其他操作,比如自动添加加密逻辑。 另外,引用[3]提到了元对象处理器接口,这可能是在插入或更新时自动填充某些字段,比如版本号。或许加密也可以在元对象处理器中处理,但需要确认是否适合。 步骤可能包括:定义加密服务接口,创建自定义TypeHandler,在实体类中指定该TypeHandler,并在MyBatis配置中注册。同时,可能需要在拦截器中处理参数,比如在更新或插入前加密数据,查询后解密数据。或者,使用TypeHandler在数据写入数据库前加密,读取时解密,这样拦截器可能不需要处理具体的加密逻辑,而是TypeHandler自动处理。 需要确保加密逻辑在数据持久化时自动应用。例如,在实体类的字段上使用@TableField(typeHandler = EncryptTypeHandler.class),这样MyBatis-Plus在读写该字段时会自动调用TypeHandler的setParameter和getResult方法,从而进行加密和解密。 同时,参考引用[5]中的配置类,用户可能需要添加自定义拦截器,但可能更直接的是使用TypeHandler。不过用户明确提到拦截器,可能需要澄清是否必须使用拦截器,或者TypeHandler是否足够。或者,拦截器用于更复杂的场景,比如全局处理所有敏感字段,而无需在每个字段上标注TypeHandler。 可能需要分步骤来:首先实现加密解密的逻辑,然后通过TypeHandler或拦截器来应用。根据引用[1]的步骤,自定义TypeHandler可能更直接。但用户的问题提到拦截器,所以可能需要结合两者,或者说明拦截器的另一种实现方式。 例如,可以创建一个实现InnerInterceptor接口的拦截器,重写beforePrepare方法,在参数设置前进行加密,在结果处理时进行解密。不过这可能比较复杂,需要处理不同的SQL操作类型,如INSERT、UPDATE、SELECT等。 总结可能的步骤:1. 创建加密工具类;2. 自定义TypeHandler处理字段级别的加密;3. 在实体类中使用该TypeHandler;4. 注册TypeHandler到MyBatis配置。或者,使用拦截器在SQL执行前后处理参数和结果集。 需要查阅MyBatis-Plus的文档,确认拦截器和TypeHandler的最佳实践。根据现有引用,引用[1]使用TypeHandler,而引用[5]使用拦截器处理乐观锁和分页。因此,可能两者都可以,但TypeHandler更适合字段级别的处理,而拦截器更适合全局处理或更复杂的逻辑。 因此,回答应该结合引用[1]中的步骤,使用TypeHandler实现加密解密,并可能提到拦截器的其他用途,或者如何结合拦截器进行更高级的处理。但用户的问题是关于拦截器实现方式,可能需要调整方法,或者说明TypeHandler是更合适的解决方案,而拦截器可能用于其他方面。 可能的混淆点:TypeHandler和拦截器的不同作用。TypeHandler用于处理单个字段的Java类型到JDBC类型的转换,适合加密解密单个字段。拦截器可以拦截整个SQL语句,处理参数和结果集,适合更广泛的操作,比如全局加密多个字段,或者根据条件动态处理。 因此,正确的实现方式可能是使用TypeHandler处理字段级别的加密,如引用[1]所述,而拦截器可能用于更复杂的场景。但用户的问题明确询问拦截器,所以可能需要解释如何通过拦截器实现,或者指出TypeHandler是更常见的方法,而拦截器可以辅助其他处理。 最终,可能需要按照引用[1]的方法,使用TypeHandler,并在回答中说明这是MyBatis-Plus推荐的字段级加密方式,同时可能提及拦截器可以用于其他方面的增强,如自动触发加密逻辑,或者在特定操作前后进行处理。</think>### 如何使用 MyBatis-Plus 3.4 拦截器实现敏感数据加密? #### 实现步骤 1. **定义加密解密工具类** 实现对称加密(如 AES)或非对称加密算法,提供加密和解密方法。例如: ```java public class EncryptUtils { public static String encrypt(String data) { /* 加密逻辑 */ } public static String decrypt(String encryptedData) { /* 解密逻辑 */ } } ``` 2. **创建自定义 TypeHandler** 继承 `BaseTypeHandler`,在数据写入数据库前加密,读取时解密。这是字段级加密的核心逻辑[^1]。 ```java public class EncryptTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { ps.setString(i, EncryptUtils.encrypt(parameter)); } @Override public String getNullableResult(ResultSet rs, String columnName) { return EncryptUtils.decrypt(rs.getString(columnName)); } // 其他重写方法... } ``` 3. **实体类标注 TypeHandler** 在敏感字段上使用 `@TableField` 注解绑定自定义 TypeHandler: ```java public class User { @TableField(typeHandler = EncryptTypeHandler.class) private String phoneNumber; // 其他字段... } ``` 4. **注册 TypeHandler** 在 MyBatis 配置文件中添加 TypeHandler 的全局注册: ```xml <typeHandlers> <typeHandler handler="com.example.EncryptTypeHandler" javaType="java.lang.String"/> </typeHandlers> ``` 5. **通过拦截器增强控制(可选)** 若需全局拦截 SQL 操作(如记录加密日志),可自定义拦截器实现 `InnerInterceptor`: ```java public class EncryptInterceptor implements InnerInterceptor { @Override public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) { // 拦截参数并处理敏感数据(需结合反射或 MetaObject 修改字段值) } } ``` 在配置类中注册拦截器: ```java @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new EncryptInterceptor()); return interceptor; } ``` #### 核心逻辑说明 - **TypeHandler 作用**:直接关联字段的读写操作,自动触发加密/解密[^1]。 - **拦截器适用场景**:补充全局操作(如审计日志),但字段级加密优先使用 TypeHandler[^5]。 #### 注意事项 - 加密字段需避免参与索引查询,否则需实现模糊加密(如哈希处理)。 - 加解密可能影响性能,建议仅对高敏感字段(如手机号、身份证号)使用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值