实现:
- 自定义注解类
- 自定义myabtis拦截器,拦截mybatis,主要涉及三个handler(StatementHandler,ParameterHandler,ResultSetHandler)
- 自定义加解密工具类
- 自定义业务处理Service(根据业务自行开发)
- 自定义注解添加再实体类及需要加解密字段上进行简单增改查测试
- 1. 自定义注解类
import java.lang.annotation.*; /** * ===================================== * *******开发部 * ===================================== * * @author 开发者 * @version 1.0-SNAPSHOT * * @date 2023/2/6 */ @Target({ElementType.TYPE, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface SensitiveEntity { }
import java.lang.annotation.*;
/**
* =====================================
* ***********开发部
* =====================================
*
* @author 开发者
* @version 1.0-SNAPSHOT
*
* @date 2023/2/6
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveField {
}
- 2. 自定义拦截器
import ********.SensitiveEntity; import ********.AesService; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; 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.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.ArrayList; import java.util.Objects; import java.util.Properties; /** * ===================================== * ***********开发部 * ===================================== * * @version 1.0-SNAPSHOT * @description * @date 2023/2/10 */ @Component @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class), @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) @Slf4j public class MyBatisInterceptor implements Interceptor { @Resource private AesService aesService; @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); //拦截sql结果处理器 if (target instanceof ResultSetHandler) { return resultDecrypt(invocation); } //拦截sql参数处理器 if (target instanceof ParameterHandler) { return parameterEncrypt(invocation); } //拦截sql语句处理器 if (target instanceof StatementHandler) { return replaceSql(invocation); } return invocation.proceed(); } /** * 对mybatis映射结果进行字段解密 * * @param invocation 参数 * @return 结果 * @throws Throwable 异常 */ private Object resultDecrypt(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))) { return resultObject; } for (Object result : resultList) { //逐一解密 aesService.decrypt(result); } //基于selectOne } else { if (needToDecrypt(resultObject)) { aesService.decrypt(resultObject); } } return resultObject; } /** * mybatis映射参数进行加密 * * @param invocation 参数 * @return 结果 * @throws Throwable 异常 */ private Object parameterEncrypt(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 (null == parameterObject) { return invocation.proceed(); } Class<?> parameterObjectClass = parameterObject.getClass(); //校验该实例的类是否被@SensitiveEntity所注解 SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveEntity.class); //未被@SensitiveEntity所注解 则为null if (Objects.isNull(sensitiveEntity)) { return invocation.proceed(); } //取出当前当前类所有字段,传入加密方法 Field[] declaredFields = parameterObjectClass.getDeclaredFields(); aesService.encrypt(declaredFields, parameterObject); return invocation.proceed(); } /** * 替换mybatis Sql中的加密Key * * @param invocation 参数 * @return 结果 * @throws Throwable 异常 */ private Object replaceSql(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); //获取到原始sql语句 String sql = boundSql.getSql(); sql = aesService.replaceAll(sql); if (null == sql){ return invocation.proceed(); } //通过反射修改sql语句 Field field = boundSql.getClass().getDeclaredField("sql"); field.setAccessible(true); field.set(boundSql, sql); return invocation.proceed(); } /** * 判断是否包含需要加解密对象 * * @param object 参数 * @return 结果 */ private boolean needToDecrypt(Object object) { Class<?> objectClass = object.getClass(); SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveEntity.class); return Objects.nonNull(sensitiveEntity); } @Override public Object plugin(Object target) { return Interceptor.super.plugin(target); } @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } }
- 3. 自定义解密工具类
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.Base64Utils; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * ===================================== * ****************开发部 * ===================================== * * @author 开发者 * @version 1.0-SNAPSHOT * @description * @date 2023/2/10 */ @Component @Slf4j public class AesFieldUtils { /** * 加密算法 */ private final String KEY_ALGORITHM = "AES"; /** * 算法/模式/补码方式 */ private final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; /** * 编码格式 */ private final String CODE = "utf-8"; /** * base64验证规则 */ private final String BASE64_RULE = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$"; /** * 正则验证对象 */ private final Pattern PATTERN = Pattern.compile(BASE64_RULE); /** * 加解密 密钥key */ @Value("${encrypt.key}") public static String encryptKey; /** * @param content 加密字符串 * @return 加密结果 */ public String encrypt(String content) { return encrypt(content, encryptKey); } /** * 加密 * * @param content 加密参数 * @param key 加密key * @return 结果字符串 */ public String encrypt(String content, String key) { //判断如果已经是base64加密字符串则返回原字符串 if (isBase64(content)) { return content; } byte[] encrypted = encrypt2bytes(content, key); if (null == encrypted || encrypted.length < 1) { log.info("加密字符串[{}]转字节为null", content); return null; } return Base64Utils.encodeToString(encrypted); } /** * @param content 加密字符串 * @param key 加密key * @return 返回加密字节 */ public byte[] encrypt2bytes(String content, String key) { try { byte[] raw = key.getBytes(CODE); SecretKeySpec secretKeySpec = new SecretKeySpec(raw, KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); return cipher.doFinal(content.getBytes(CODE)); } catch (Exception e) { log.error("failed to encrypt: {} of {}", content, e); return null; } } /** * @param content 加密字符串 * @return 返回加密结果 */ public String decrypt(String content) { try { return decrypt(content, encryptKey); } catch (Exception e) { log.error("failed to decrypt: {}, e: {}", content, e); return null; } } /** * 解密 * * @param content 解密字符串 * @param key 解密key * @return 解密结果 */ public String decrypt(String content, String key) throws Exception { //不是base64格式字符串则不进行解密 if (!isBase64(content)) { return content; } return decrypt(Base64Utils.decodeFromString(content), key); } /** * @param content 解密字节 * @param key 解密key * @return 返回解密内容 */ public String decrypt(byte[] content, String key) throws Exception { if (key == null) { log.error("AES key should not be null"); return null; } byte[] raw = key.getBytes(CODE); SecretKeySpec keySpec = new SecretKeySpec(raw, KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec); try { byte[] original = cipher.doFinal(content); return new String(original, CODE); } catch (Exception e) { log.error("failed to decrypt content: {}/ key: {}, e: {}", content, key, e); return null; } } /** * 判断是否为 base64加密 * * @param str 参数 * @return 结果 */ private boolean isBase64(String str) { Matcher matcher = PATTERN.matcher(str); return matcher.matches(); } }
- 4. 自定义业务处理Service
import *****************.SensitiveField; import *****************.AesService; import *****************.AesFieldUtils; import *****************.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.lang.reflect.Field; import java.util.Objects; /** * ===================================== * *****************开发部 * ===================================== * * @author 开发者 * @version 1.0-SNAPSHOT * @description * @date 2023/2/10 */ @Service public class AesServiceImpl implements AesService { @Value("${aes.key}") private String key; @Value("${aes.keyField}") private String keyField; @Resource private AesFieldUtils aesFieldUtils; @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)) { continue; } field.setAccessible(true); Object object = field.get(paramsObject); //暂时只实现String类型的加密 if (object instanceof String) { String value = (String) object; //如果映射字段值为空,并且以==结尾则跳过不进行加密 if (!StringUtils.noEmpty(value)) { continue; } //加密 这里我使用自定义的AES加密工具 field.set(paramsObject, aesFieldUtils.encrypt(value, key)); } } return paramsObject; } @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)) { continue; } field.setAccessible(true); Object object = field.get(result); //只支持String的解密 if (object instanceof String) { String value = (String) object; //如果映射字段值为空,并且不已==结尾则跳过不进行解密 if (!StringUtils.noEmpty(value)) { continue; } //对注解的字段进行逐一解密 field.set(result, aesFieldUtils.decrypt(value, key)); } } return result; } @Override public String replaceAll(String sql) { if (sql.contains(keyField)) { return sql.replaceAll(keyField, key); } return null; } }
- 5. 测试代码
import **************.SensitiveEntity; import **************.SensitiveField; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; @SensitiveEntity @TableName("t_users") public class Users extends Model<Users> { /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.id * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.user_id * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ private String userId; /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.user_name * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ private String userName; /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.nick_name * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ private String nickName; /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.password * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ @SensitiveField private String password; /** * * This field was generated by MyBatis Generator. * This field corresponds to the database column t_users.pwd_duration * * @mbg.generated Wed Feb 01 10:03:44 CST 2023 */ private String pwdDuration; @SensitiveField private String birth;
}
@Test
public void add(){
Users user = new Users();
user.setUserId("test03");
user.setUserName("小明004");
user.setNickName("test03");
user.setPassword("12343454123");
user.setPwdDuration("124124124214");
usersService.insert(user);
}
@Test
public void update(){
Users user = new Users();
user.setUserId("test03");
user.setUserName("小明03");
user.setNickName("test03");
user.setPassword("1252525125123");
user.setPwdDuration("1252525125121");
usersService.updateByPrimaryKeySelective(user);
}
@Test
public void queryTest(){
System.out.println(JSON.toJSON(usersService.selectByPrimaryKey(3)));
}
本文原创:如有使用请标明出处