mybatis用攔截器實(shí)現(xiàn)字段加解密全過程
前言
根據(jù)公司業(yè)務(wù)需要,靈活對(duì)客戶敏感信息進(jìn)行加解密,這里采用mybatis攔截器進(jìn)行簡單實(shí)現(xiàn)個(gè)demo。
攔截器的使用
// 執(zhí)行 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) // 請(qǐng)求參數(shù)處理 ParameterHandler (getParameterObject, setParameters) // 返回結(jié)果集處理 ResultSetHandler (handleResultSets, handleOutputParameters) // SQL語句構(gòu)建 StatementHandler (prepare, parameterize, batch, update, query)
我們要實(shí)現(xiàn)數(shù)據(jù)加密,進(jìn)入數(shù)據(jù)庫的字段不能是真實(shí)的數(shù)據(jù),但是返回來的數(shù)據(jù)要真實(shí)可用,所以我們需要針對(duì) Parameter 和 ResultSet 兩種類型處理,同時(shí)為了更靈活的使用,我們需要自定義注解。
/**
*需要加解密的字段注解
**/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encryption {
String encryptionType() default "";
}
編寫一下加解密算法(隨便找的)
**
* @desc: AES對(duì)稱加密,對(duì)明文進(jìn)行加密、解密處理
* @author:
* @createTime: 20231014 上午9:54:52
* @version: v0.0.1
*/
public class AESUtil {
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* @desc: AES對(duì)稱-加密操作
* @version: v0.0.1
* @param keyStr 進(jìn)行了Base64編碼的秘鑰
* @param data 需要進(jìn)行加密的原文
* @return String 數(shù)據(jù)密文,加密后的數(shù)據(jù),進(jìn)行了Base64的編碼
*/
public static String encrypt(String keyStr, String data) throws Exception {
// 轉(zhuǎn)換密鑰
Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 加密
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(result);
}
/**
* @desc: AES對(duì)稱-解密操作
* @version: v0.0.1
* @param keyStr 進(jìn)行了Base64編碼的秘鑰
* @param data 需要解密的數(shù)據(jù)<span style="color:red;">(數(shù)據(jù)必須是通過AES進(jìn)行加密后,對(duì)加密數(shù)據(jù)Base64編碼的數(shù)據(jù))</span>
* @return String 返回解密后的原文
*/
public static String decrypt(String keyStr, String data) throws Exception {
// 轉(zhuǎn)換密鑰
Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 解密
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(Base64.getDecoder().decode(data));
return new String(result);
}
/**
* @desc: 生成AES的秘鑰,秘鑰進(jìn)行了Base64編碼的字符串
* @version: v0.0.1
* @return String 對(duì)生成的秘鑰進(jìn)行了Base64編碼的字符串
*/
public static String keyGenerate() throws Exception {
// 生成密鑰
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
keyGenerator.init(new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyBytes = secretKey.getEncoded();
return Base64.getEncoder().encodeToString(keyBytes);
}
public static void main(String[] args) throws Exception {
System.out.println(
keyGenerate()
);
}
}
編寫一下加解密接口
/**
* 加解密處理接口
*/
public interface CipherHandler {
/**
* 加密
* @param data 需要加密的數(shù)據(jù)
* @return 加密結(jié)果
*/
String encrypt(String data) throws Exception;
/**
* 解密
* @param data 需要加密的數(shù)據(jù)
* @return 解密結(jié)果
*/
String decrypt(String data) throws Exception;
}
public class AEScipher implements CipherHandler{
private final static String keyStr="glRwFSBcKzppYVQiwT8M/Q==";
@Override
public String encrypt(String data) throws Exception {
return AESUtil.encrypt(this.keyStr,data);
}
@Override
public String decrypt(String data) throws Exception {
return AESUtil.decrypt(this.keyStr,data);
}
public static CipherHandler getAEScipher(){
return new AEScipher();
}
}
接下來寫一下加解密的攔截器
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Component
@Slf4j
public class ParameterInterceptor implements Interceptor {
private CipherHandler cipherHandler = AEScipher.getAEScipher();
@Override
public Object intercept(Invocation invocation) throws Throwable {
DefaultParameterHandler parameterHandler = (DefaultParameterHandler) invocation.getTarget();
// 獲取參數(shù)對(duì)像,即 mapper 中 paramsType 的實(shí)例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
// 取出實(shí)例
Object parameterObject = parameterField.get(parameterHandler);
try {
// 搜索該方法中是否有需要加密的字段
List<Field> fieldList = searchParamAnnotation(parameterHandler);
//加密
if(!CommonHelper.isEmpty(fieldList)){
dealParamEncrypt(fieldList,parameterObject);
}
parameterField.set(parameterHandler, parameterObject);
PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
parameterHandler.setParameters(ps);
} catch (Exception e) {
log.info(e.getMessage());
}
return invocation.proceed();
}
/**
* 查找需要需要加密字段
* @param parameterHandler
* @return
* @throws Exception
*/
private List<Field> searchParamAnnotation(ParameterHandler parameterHandler) throws Exception {
Class<DefaultParameterHandler> handlerClass = DefaultParameterHandler.class;
Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement");
mappedStatementFiled.setAccessible(true);
MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler);
String methodName = mappedStatement.getId();
// 獲取Mapper類對(duì)象
Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.')));
methodName = methodName.substring(methodName.lastIndexOf('.') + 1);
Method[] methods = mapperClass.getDeclaredMethods();
Method method = null;
for (Method m : methods) {
if (m.getName().equals(methodName)) {
method = m;
break;
}
}
List<Field> fieldList = new ArrayList<>();
if (method != null) {
Annotation[][] pa = method.getParameterAnnotations();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < pa.length; i++) {
Parameter parameter = parameters[i];
String typeName = parameter.getParameterizedType().getTypeName();
// 去除泛型導(dǎo)致的ClassNotFoundException
Class<?> parameterClass = Class.forName(typeName.contains("<") ? typeName.substring(0, typeName.indexOf("<")) : typeName);
Field[] declaredFields = parameterClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Annotation annotation = declaredField.getAnnotation(Encryption.class);
if(!CommonHelper.isEmpty(annotation))fieldList.add(declaredField);
}
}
}
return fieldList;
}
/**
* 處理加密類
* @param fields
* @param parameterObject
* @throws Exception
*/
private void dealParamEncrypt(List<Field> fields,Object parameterObject) throws Exception {
fields.forEach(declaredField->{
declaredField.setAccessible(true);
Object o = null;
try {
o = declaredField.get(parameterObject);
declaredField.set(parameterObject,cipherHandler.encrypt(o.toString()));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
}
}
查詢結(jié)果解密
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class ResultSetInterceptor implements Interceptor {
private CipherHandler cipherHandler = AEScipher.getAEScipher();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 取出查詢的結(jié)果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
// 基于selectList
if (resultObject instanceof List<?>) {
List<?> resultList = (List<?>) resultObject;
if (!CommonHelper.isEmpty(resultList)) {
for (Object obj : resultList) {
toDecrypt(obj);
}
}
} else {
toDecrypt(resultObject);
}
return resultObject;
}
private void toDecrypt(Object object) throws Exception {
Class<?> objectClass = object.getClass();
Field[] declaredFields = objectClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Annotation annotation = declaredField.getAnnotation(Encryption.class);
if(!CommonHelper.isEmpty(annotation)){
declaredField.setAccessible(true);
declaredField.set(object,cipherHandler.decrypt(declaredField.get(object).toString()));
}
}
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o,this);
}
@Override
public void setProperties(Properties properties) {
}
}
判空工具類
public class CommonHelper {
public static boolean isEmpty(Object o){
if(o==null)return true;
if(o instanceof String){
return ((String) o).isEmpty();
}else if(o instanceof List){
return CollectionUtils.isEmpty((List) o) || ((List<?>) o).size()==0 ;
}else if(o instanceof Map){
return CollectionUtils.isEmpty((Map) o) || ((Map<?,?>) o).size()==0 ;
}else {
return Objects.isNull(o);
}
}
}
測試結(jié)果:
- 插入數(shù)據(jù)


- 查詢數(shù)據(jù):

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC框架中使用Filter實(shí)現(xiàn)請(qǐng)求日志打印方式
這篇文章主要介紹了SpringMVC框架中使用Filter實(shí)現(xiàn)請(qǐng)求日志打印方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
基于SpringBoot實(shí)現(xiàn)HTTP請(qǐng)求簽名驗(yàn)證機(jī)制
在分布式系統(tǒng)交互中,API接口的安全性至關(guān)重要,本文將深入解析基于Spring Boot實(shí)現(xiàn)的HTTP請(qǐng)求簽名驗(yàn)證機(jī)制,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
feign 調(diào)用第三方服務(wù)中部分特殊符號(hào)未轉(zhuǎn)義問題
這篇文章主要介紹了feign 調(diào)用第三方服務(wù)中部分特殊符號(hào)未轉(zhuǎn)義問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

