javascript
在SpringBoot项目中,自定义注解+拦截器优雅的实现数据的加解密!
點擊上方藍色字體,選擇“標星公眾號”
優質文章,第一時間送達
關注公眾號后臺回復pay或mall獲取實戰項目資料+視頻
作者:CoderTanzJ
blog.csdn.net/bbcckkl/article/details/104069487
在實際生產項目中,經常需要對如身份證信息、手機號、真實姓名等的敏感數據進行加密數據庫存儲,但在業務代碼中對敏感信息進行手動加解密則十分不優雅,甚至會存在錯加密、漏加密、業務人員需要知道實際的加密規則等的情況。
本文將介紹使用springboot+mybatis攔截器+自定義注解的形式對敏感數據進行存儲前攔截加密的詳細過程。
一、什么是Mybatis Plugin
在mybatis官方文檔中,對于Mybatis plugin的的介紹是這樣的:
MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:
//語句執行攔截 Executor?(update,?query,?flushStatements,?commit,?rollback,?getTransaction,?close,?isClosed)//?參數獲取、設置時進行攔截 ParameterHandler?(getParameterObject,?setParameters)//?對返回結果進行攔截 ResultSetHandler?(handleResultSets,?handleOutputParameters)//sql語句攔截 StatementHandler?(prepare,?parameterize,?batch,?update,?query)簡而言之,即在執行sql的整個周期中,我們可以任意切入到某一點對sql的參數、sql執行結果集、sql語句本身等進行切面處理。基于這個特性,我們便可以使用其對我們需要進行加密的數據進行切面統一加密處理了(分頁插件 pageHelper 就是這樣實現數據庫分頁查詢的)。
二、實現基于注解的敏感信息加解密攔截器
2.1 實現思路
對于數據的加密與解密,應當存在兩個攔截器對數據進行攔截操作
參照官方文檔,因此此處我們應當使用ParameterHandler攔截器對入參進行加密
使用ResultSetHandler攔截器對出參進行解密操作。
目標需要加密、解密的字段可能需要靈活變更,此時我們定義一個注解,對需要加密的字段進行注解,那么便可以配合攔截器對需要的數據進行加密與解密操作了。
mybatis的interceptor接口有以下方法需要實現。
public?interface?Interceptor?{//主要參數攔截方法Object?intercept(Invocation?invocation)?throws?Throwable;//mybatis插件鏈default?Object?plugin(Object?target)?{return?Plugin.wrap(target,?this);}//自定義插件配置文件方法default?void?setProperties(Properties?properties)?{}}2.2 定義需要加密解密的敏感信息注解
定義注解敏感信息類(如實體類POJO\PO)的注解
/***?注解敏感信息類的注解*/ @Inherited @Target({?ElementType.TYPE?}) @Retention(RetentionPolicy.RUNTIME) public?@interface?SensitiveData?{ }定義注解敏感信息類中敏感字段的注解
/***?注解敏感信息類中敏感字段的注解*/ @Inherited @Target({?ElementType.Field?}) @Retention(RetentionPolicy.RUNTIME) public?@interface?SensitiveField?{ }2.3 定義加密接口及其實現類
定義加密接口,方便以后拓展加密方法(如AES加密算法拓展支持PBE算法,只需要注入時指定一下便可)
public?interface?EncryptUtil?{/***?加密**?@param?declaredFields?paramsObject所聲明的字段*?@param?paramsObject???mapper中paramsType的實例*?@return?T*?@throws?IllegalAccessException?字段不可訪問異常*/<T>?T?encrypt(Field[]?declaredFields,?T?paramsObject)?throws?IllegalAccessException; }EncryptUtil 的AES加密實現類,此處AESUtil為自封裝的AES加密工具,需要的小伙伴可以自行封裝,本文不提供。(搜索公眾號Java知音,回復“2021”,送你一份Java面試題寶典)
@Component public?class?AESEncrypt?implements?EncryptUtil?{@AutowiredAESUtil?aesUtil;/***?加密**?@param?declaredFields?paramsObject所聲明的字段*?@param?paramsObject???mapper中paramsType的實例*?@return?T*?@throws?IllegalAccessException?字段不可訪問異常*/@Overridepublic?<T>?T?encrypt(Field[]?declaredFields,?T?paramsObject)?throws?IllegalAccessException?{for?(Field?field?:?declaredFields)?{//取出所有被EncryptDecryptField注解的字段SensitiveField?sensitiveField?=?field.getAnnotation(SensitiveField.class);if?(!Objects.isNull(sensitiveField))?{field.setAccessible(true);Object?object?=?field.get(paramsObject);//暫時只實現String類型的加密if?(object?instanceof?String)?{String?value?=?(String)?object;//加密??這里我使用自定義的AES加密工具field.set(paramsObject,?aesUtil.encrypt(value));}}}return?paramsObject;} }2.4 實現入參加密攔截器
Myabtis包中的org.apache.ibatis.plugin.Interceptor攔截器接口要求我們實現以下三個方法
public?interface?Interceptor?{//核心攔截邏輯Object?intercept(Invocation?invocation)?throws?Throwable;//攔截器鏈default?Object?plugin(Object?target)?{return?Plugin.wrap(target,?this);}//自定義配置文件操作default?void?setProperties(Properties?properties)?{?}}因此,參考官方文檔的示例,我們自定義一個入參加密攔截器。
@Intercepts 注解開啟攔截器,@Signature 注解定義攔截器的實際類型。
@Signature中
type 屬性指定當前攔截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一種
method 屬性指定使用以上四種類型的具體方法(可進入class內部查看其方法)。
args 屬性指定預編譯語句
此處我們使用了?ParameterHandler.setParamters()方法,攔截mapper.xml中paramsType的實例(即在每個含有paramsType屬性mapper語句中,都執行該攔截器,對paramsType的實例進行攔截處理)
/***?加密攔截器*?注意@Component注解一定要加上**?@author?:?tanzj*?@date?:?2020/1/19.*/ @Slf4j @Component @Intercepts({@Signature(type?=?ParameterHandler.class,?method?=?"setParameters",?args?=?PreparedStatement.class), }) public?class?EncryptInterceptor?implements?Interceptor?{private?final?EncryptDecryptUtil?encryptUtil;@Autowiredpublic?EncryptInterceptor(EncryptDecryptUtil?encryptUtil)?{this.encryptUtil?=?encryptUtil;}@Override@Overridepublic?Object?intercept(Invocation?invocation)?throws?Throwable?{//@Signature?指定了?type=?parameterHandler?后,這里的?invocation.getTarget()?便是parameterHandler?//若指定ResultSetHandler?,這里則能強轉為ResultSetHandlerParameterHandler?parameterHandler?=?(ParameterHandler)?invocation.getTarget();//?獲取參數對像,即?mapper?中?paramsType?的實例Field?parameterField?=?parameterHandler.getClass().getDeclaredField("parameterObject");parameterField.setAccessible(true);//取出實例Object?parameterObject?=?parameterField.get(parameterHandler);if?(parameterObject?!=?null)?{Class<?>?parameterObjectClass?=?parameterObject.getClass();//校驗該實例的類是否被@SensitiveData所注解SensitiveData?sensitiveData?=?AnnotationUtils.findAnnotation(parameterObjectClass,?SensitiveData.class);if?(Objects.nonNull(sensitiveData))?{//取出當前當前類所有字段,傳入加密方法Field[]?declaredFields?=?parameterObjectClass.getDeclaredFields();encryptUtil.encrypt(declaredFields,?parameterObject);}}return?invocation.proceed();}/***?切記配置,否則當前攔截器不會加入攔截器鏈*/@Overridepublic?Object?plugin(Object?o)?{return?Plugin.wrap(o,?this);}//自定義配置寫入,沒有自定義配置的可以直接置空此方法@Overridepublic?void?setProperties(Properties?properties)?{} }至此完成自定義加密攔截加密。
2.5 定義解密接口及其實現類
解密接口,其中result為mapper.xml中resultType的實例。
public?interface?DecryptUtil?{/***?解密**?@param?result?resultType的實例*?@return?T*?@throws?IllegalAccessException?字段不可訪問異常*/<T>?T?decrypt(T?result)?throws?IllegalAccessException;}解密接口AES工具解密實現類
public?class?AESDecrypt?implements?DecryptUtil?{@AutowiredAESUtil?aesUtil;/***?解密**?@param?result?resultType的實例*?@return?T*?@throws?IllegalAccessException?字段不可訪問異常*/@Overridepublic?<T>?T?decrypt(T?result)?throws?IllegalAccessException?{//取出resultType的類Class<?>?resultClass?=?result.getClass();Field[]?declaredFields?=?resultClass.getDeclaredFields();for?(Field?field?:?declaredFields)?{//取出所有被EncryptDecryptField注解的字段SensitiveField?sensitiveField?=?field.getAnnotation(SensitiveField.class);if?(!Objects.isNull(sensitiveField))?{field.setAccessible(true);Object?object?=?field.get(result);//只支持String的解密if?(object?instanceof?String)?{String?value?=?(String)?object;//對注解的字段進行逐一解密field.set(result,?aesUtil.decrypt(value));}}}return?result;} }2.6 定義出參解密攔截器
@Slf4j @Component @Intercepts({@Signature(type?=?ResultSetHandler.class,?method?=?"handleResultSets",?args?=?{Statement.class}) }) public?class?DecryptInterceptor?implements?Interceptor?{@AutowiredDecryptUtil?aesDecrypt;@Overridepublic?Object?intercept(Invocation?invocation)?throws?Throwable?{//取出查詢的結果Object?resultObject?=?invocation.proceed();if?(Objects.isNull(resultObject))?{return?null;}//基于selectListif?(resultObject?instanceof?ArrayList)?{ArrayList?resultList?=?(ArrayList)?resultObject;if?(!CollectionUtils.isEmpty(resultList)?&&?needToDecrypt(resultList.get(0)))?{for?(Object?result?:?resultList)?{//逐一解密aesDecrypt.decrypt(result);}}//基于selectOne}?else?{if?(needToDecrypt(resultObject))?{aesDecrypt.decrypt(resultObject);}}return?resultObject;}private?boolean?needToDecrypt(Object?object)?{Class<?>?objectClass?=?object.getClass();SensitiveData?sensitiveData?=?AnnotationUtils.findAnnotation(objectClass,?SensitiveData.class);return?Objects.nonNull(sensitiveData);}@Overridepublic?Object?plugin(Object?target)?{return?Plugin.wrap(target,?this);}@Overridepublic?void?setProperties(Properties?properties)?{} }至此完成解密攔截器的配置工作。
3、注解實體類中需要加解密的字段
此時在mapper中,指定paramType=User resultType=User 便可實現脫離業務層,基于mybatis攔截器的加解密操作。
有熱門推薦???? TikTok二面:“聊聊二維碼掃碼登錄的原理”。用了這么多年的 Postman,竟然用錯了~19 個接私活平臺匯總,今天我們只聊用技術掙錢 Spring Boot實戰:攔截器與過濾器詳解與使用!!! 換掉Logback!Log4j2的異步性能已經無敵了,還不快試試 Linux的常用命令就是記不住,還在百度找?于是推出了這套教程談談我學習設計模式的感悟!受益匪淺 服務端如何防止重復支付,簡單實用! Java 做微服務能像 Go 一樣快嗎? Java項目中常見的異常產生原因及處理辦法 你知道 Java 是如何實現線程間通信的嗎?點擊閱讀原文,前往學習SpringCloud實戰項目總結
以上是生活随笔為你收集整理的在SpringBoot项目中,自定义注解+拦截器优雅的实现数据的加解密!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用友U8安装教程
- 下一篇: 最全面的SpringMVC教程(二)——