数据安全之MySQL数据加解密的实现方案
在我們?nèi)粘5臉I(yè)務(wù)需求中,經(jīng)常會(huì)遇到需要對(duì)存儲(chǔ)的用戶敏感數(shù)據(jù)進(jìn)行加密處理的場(chǎng)景,如用戶的身份信息、住址、身份證號(hào)等等,本文我們就討論下,業(yè)務(wù)系統(tǒng)(后端)如何實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)(基于MySQL)的加解密功能。
技術(shù)棧:springboot、mybatis、mysql等
方案一:基于spring aop攔截mybatis mapper.
第一步:定義注解@Encrypt
@Target(ElementType.METHOD)//注解的范圍是類、接口、枚舉的方法上 @Retention(RetentionPolicy.RUNTIME)//被虛擬機(jī)保存,可用反射機(jī)制讀取 public @interface Encrypt{/*** 入?yún)⑿枰用艿淖侄? @return*/String[] paramFields() default {};/*** 響應(yīng)參數(shù)需解密的字段* @return*/String[] respFields() default {}; }第二步:開(kāi)發(fā)攔截器處理類
@Slf4j @Aspect @Component public class EncryptAspect {@Pointcut("@annotation(com.xxx.annotation.Encrypt)")public void encryPointCut() {}@Around("encryPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();final Sm4Intercept annotation = method.getAnnotation(Encrypt.class);if (null == annotation) {return joinPoint.proceed();}//加密入?yún)?duì)象屬性值encryptRequest(joinPoint, annotation);//執(zhí)行目標(biāo)方法Object response = joinPoint.proceed();//解密響應(yīng)結(jié)果對(duì)象的屬性值decryptResponse(response, annotation);return response;}/*** 加密請(qǐng)求入?yún)?duì)象** @param joinPoint* @param annotation*/private void encryptRequest(ProceedingJoinPoint joinPoint, Sm4Intercept annotation) {//獲取接口的入?yún)⒘斜韋inal Object[] params = joinPoint.getArgs();if (!CollectionUtil.isEmpty(params)) {//接口入?yún)bject param = params[0];//接口入?yún)?duì)象的屬性列表Field[] fields = param.getClass().getDeclaredFields();if (!CollectionUtil.isEmpty(annotation.paramFields())) {//遍歷加密入?yún)⒌膶傩灾礎(chǔ)rrays.stream(annotation.paramFields()).forEach(target -> {Field field = Arrays.stream(fields).filter(f -> f.getName().equals(target)).findFirst().orElse(null);if (null != field) {//反射獲取目標(biāo)屬性值Object fieldValue = getFieldValue(param, field.getName());if (null != fieldValue) {String encryFieldValue = EncryptUtil.encryptEcb(key, fieldValue.toString());log.info("類{}的屬性{}的值{}已被加密為{}", param.getClass().getName(), target, fieldValue, encryFieldValue);setFieldValue(param, field.getName(), encryFieldValue);}}});}}}/*** 解密響應(yīng)結(jié)果對(duì)象的屬性值** @param object* @param annotation*/private void decryptResponse(Object object, Sm4Intercept annotation) {//返回結(jié)果是list時(shí)if (object instanceof List) {decryptListObject((List) object, annotation);return;}//返回結(jié)果為單對(duì)象時(shí)decryptObject(object, annotation);}/*** 解密list中對(duì)象的屬性值** @param list* @param annotation*/private void decryptListObject(List list, Sm4Intercept annotation) {list.stream().forEach(record -> decryptObject(record, annotation));}/*** 解密單對(duì)象的屬性值** @param record* @param annotation*/private void decryptObject(Object record, Sm4Intercept annotation) {//接口返回對(duì)象的屬性列表Field[] fields = record.getClass().getDeclaredFields();if (!CollectionUtil.isEmpty(annotation.respFields())) {//遍歷加密入?yún)⒌膶傩灾礎(chǔ)rrays.stream(annotation.respFields()).forEach(target -> {Field field = Arrays.stream(fields).filter(f -> f.getName().equals(target)).findFirst().orElse(null);if (null != field) {//反射獲取目標(biāo)屬性值Object fieldValue = getFieldValue(record, field.getName());if (null != fieldValue) {String decryFieldValue = EncryptUtil.decryptEcb(key, fieldValue.toString());log.info("類{}的屬性{}的值{}已被解密為{}", record.getClass().getName(), target, fieldValue, decryFieldValue);setFieldValue(record, field.getName(), decryFieldValue);}}});}}/*** 通過(guò)反射,用屬性名稱獲得屬性值** @param thisClass 需要獲取屬性值的類* @param fieldName 該類的屬性名稱* @return*/private Object getFieldValue(Object thisClass, String fieldName) {Object value = new Object();try {Method method = thisClass.getClass().getMethod(getMethodName(fieldName, "get"));value = method.invoke(thisClass);} catch (Exception e) {}return value;}/*** 通過(guò)反射,設(shè)置屬性值** @param thisClass* @param fieldName* @param fieldValue*/private void setFieldValue(Object thisClass, String fieldName, Object fieldValue) {try {Method method = thisClass.getClass().getMethod(getMethodName(fieldName, "set"), String.class);method.invoke(thisClass, fieldValue);} catch (Exception e) {}}/*** 獲取方法名稱(getXXX,setXXX)** @param fieldName* @param methodPrefix* @return*/private String getMethodName(String fieldName, String methodPrefix) {return methodPrefix.concat(fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));} }第三步:mapper中定義注解@Encrypt進(jìn)行數(shù)據(jù)攔截
@Mapper public interface UserInfoMapper extends BaseMapper<UserInfo> {/*** 如果查詢條件中包含username,則在mapper執(zhí)行前進(jìn)行加密* 如果返回?cái)?shù)據(jù)中包含username及address等信息,則進(jìn)行解密 * @param vo* @return*/@Encrypt(paramFields = {"userName"}, respFields = {"userName", "address"})List<UserInfo> findUserInfo(UserSearchVo vo); }這樣,便實(shí)現(xiàn)了在數(shù)據(jù)查詢(或更新、插入等)時(shí),完成入?yún)⒓胺祷財(cái)?shù)據(jù)的加解密操作。不過(guò)這種處理方式僅限于數(shù)據(jù)操作是通過(guò)Dao的mapper接口調(diào)用時(shí),如果想處理更多場(chǎng)景,如通過(guò)mybatis-plus的Wraper方式進(jìn)行數(shù)據(jù)處理時(shí),則考慮用后面的第二種處理方式。
方案二:基于mybatis自帶的擴(kuò)展插件(plugins)實(shí)現(xiàn)
MyBatis 允許你在映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
這些類中方法的細(xì)節(jié)可以通過(guò)查看每個(gè)方法的簽名來(lái)發(fā)現(xiàn),或者直接查看 MyBatis 發(fā)行包中的源代碼。 如果你想做的不僅僅是監(jiān)控方法的調(diào)用,那么你最好相當(dāng)了解要重寫的方法的行為。 因?yàn)樵谠噲D修改或重寫已有方法的行為時(shí),很可能會(huì)破壞 MyBatis 的核心模塊。 這些都是更底層的類和方法,所以使用插件的時(shí)候要特別當(dāng)心。
通過(guò) MyBatis 提供的強(qiáng)大機(jī)制,使用插件是非常簡(jiǎn)單的,只需實(shí)現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。
?插件的使用參考實(shí)現(xiàn)如下(mybatis官方文檔)
@Slf4j @Component @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })}) public class ExamplePlugin implements Interceptor {private Properties properties = new Properties();public Object intercept(Invocation invocation) throws Throwable {// implement pre processing if needObject returnObject = invocation.proceed();// implement post processing if needreturn returnObject;}public void setProperties(Properties properties) {this.properties = properties;} }即在mybatis的Executor的query方法執(zhí)行前后進(jìn)行攔截,如事先定義好需要加解密的“配置規(guī)則”(如“對(duì)哪個(gè)表的哪些字段需要加解密”、“方法的出入?yún)⑿杓咏饷艿淖侄巍钡鹊?#xff09;,然后攔截sql的請(qǐng)求參數(shù)及執(zhí)行的返回結(jié)果,對(duì)其進(jìn)行相應(yīng)的數(shù)據(jù)加解密操作。
本文的核心實(shí)現(xiàn)思路都是圍繞spring aop進(jìn)行實(shí)現(xiàn)的,足以說(shuō)明aop思想的強(qiáng)大之處,大家平時(shí)學(xué)習(xí)工作中一定要勤學(xué)、多用、多練!希望本文可以幫助到有需要的朋友們!
總結(jié)
以上是生活随笔為你收集整理的数据安全之MySQL数据加解密的实现方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微信小程序纯前端生成海报并保存本地
- 下一篇: 阿里巴巴2018秋招总结