SpringBoot實(shí)現(xiàn)數(shù)據(jù)加密脫敏的示例代碼
SpringBoot 實(shí)現(xiàn)數(shù)據(jù)加密脫敏(注解 + 反射 + AOP)
場景:響應(yīng)政府要求,商業(yè)軟件應(yīng)保證用戶基本信息不被泄露
,不能直接展示
用戶手機(jī)號,身份證,地址等敏感信息。
根據(jù)上面場景描述,我們可以分析出兩個點(diǎn)。
- 不被泄露說明用戶信息應(yīng)被加密儲存;
- 不能直接展示說明用戶信息應(yīng)脫敏展示;
解決方案
傻瓜式編程:將項目中關(guān)于用戶信息實(shí)體類的字段,比如姓名,手機(jī)號,身份證,地址等,在新增進(jìn)數(shù)據(jù)庫之前,對數(shù)據(jù)進(jìn)行加密處理;在列表中展示用戶信息時,對數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行解密脫敏,然后返回給前端;
切入式編程:將項目中關(guān)于用戶信息實(shí)體類的字段用注解給標(biāo)記,新增用戶信息實(shí)體類(這里我們用UserBO來表示,給UserBO里面的name,phone字段添加@EncryptField),返回用戶信息實(shí)體類(這里我們用UserDO來表示,給UserDO里面的name,phone字段添加@DecryptField);然后利用@EncryptField,@DecryptField做為切入點(diǎn),以切面的形式實(shí)現(xiàn)加密,解密脫敏;
傻瓜式編程不是說傻,而是相當(dāng)于切入式編程,傻瓜式編程需要對用戶信息相關(guān)的所有接口進(jìn)行加密,解密脫敏的邏輯處理,這里改動的地方就比較多,風(fēng)險高,重復(fù)操作相同的邏輯,工作量大,后期不好維護(hù);切入式編程只需要對用戶信息字段添加注解,對有注解的字段統(tǒng)一進(jìn)行加密,解密脫敏邏輯處理,操作方便,高聚合,易維護(hù);
方案實(shí)現(xiàn)
傻瓜式編程沒什么難度,這里我給大家有切入式編程來實(shí)現(xiàn);在實(shí)現(xiàn)之前,跟大家預(yù)熱一下注解,反射,AOP的知識;
注解實(shí)戰(zhàn)
創(chuàng)建注解
創(chuàng)建一個只能標(biāo)記在方法上的注解:
package com.weige.javaskillpoint.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) //METHOD 說明該注解只能用在方法上 @Retention(RetentionPolicy.RUNTIME) //RUNTIME 說明該注解在運(yùn)行時生效 public @interface Encryption { }
創(chuàng)建一個只能標(biāo)記在字段上的注解:
package com.weige.javaskillpoint.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) //FIELD 說明該注解只能用在字段上 @Retention(RetentionPolicy.RUNTIME) //RUNTIME 說明該注解在運(yùn)行時生效 public @interface EncryptField { }
創(chuàng)建一個標(biāo)記在字段上,且有值的注解:
package com.weige.javaskillpoint.annotation; import com.weige.javaskillpoint.enums.DesensitizationEnum; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface DecryptField { // 注解是可以有值的,這里可以為數(shù)組,String,枚舉等類型 // DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); 這里的field是指當(dāng)前標(biāo)記的字段 DesensitizationEnum value(); }
注解使用
創(chuàng)建枚舉
package com.weige.javaskillpoint.enums; public enum DesensitizationEnum { name, // 用戶信息姓名脫敏 address, // 用戶信息地址脫敏 phone; // 用戶信息手機(jī)號脫敏 }
創(chuàng)建UserDO類
package com.weige.javaskillpoint.entity; import com.weige.javaskillpoint.annotation.DecryptField; import com.weige.javaskillpoint.enums.DesensitizationEnum; import com.weige.javaskillpoint.utils.AesUtil; import java.lang.reflect.Field; // 用戶信息返回實(shí)體類 public class UserDO { @DecryptField(DesensitizationEnum.name) private String name; @DecryptField(DesensitizationEnum.address) private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public UserDO(String name, String address) { this.name = name; this.address = address; } public static void main(String[] args) throws IllegalAccessException { // 生成并初始化對象 UserDO userDO = new UserDO("夢想是什么","湖北省武漢市"); // 反射獲取當(dāng)前對象的所有字段 Field[] fields = userDO.getClass().getDeclaredFields(); // 遍歷字段 for (Field field : fields) { // 判斷字段上是否存在@DecryptField注解 boolean hasSecureField = field.isAnnotationPresent(DecryptField.class); // 存在 if (hasSecureField) { // 暴力破解 不然操作不了權(quán)限為private的字段 field.setAccessible(true); // 如果當(dāng)前字段在userDo中不為空 即name,address字段有值 if (field.get(userDO) != null) { // 獲取字段上注解的value值 DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); // 控制臺輸出 System.out.println(desensitizationEnum); // 根據(jù)不同的value值 我們可以對字段進(jìn)行不同邏輯的脫敏 比如姓名脫敏-魏*,手機(jī)號脫敏-187****2275 } } } } }
反射實(shí)戰(zhàn)
創(chuàng)建UserBO類
package com.weige.javaskillpoint.entity; import com.weige.javaskillpoint.annotation.EncryptField; import java.lang.reflect.Field; // 用戶信息新增實(shí)體類 public class UserBO { @EncryptField private String name; @EncryptField private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public UserBO(String name, String address) { this.name = name; this.address = address; } @Override public String toString() { return "UserBO{" + "name='" + name + '\'' + ", address='" + address + '\'' + '}'; } public static void main(String[] args) throws IllegalAccessException { UserBO userBO = new UserBO("周傳雄","湖北省武漢市"); Field[] fields = userBO.getClass().getDeclaredFields(); for (Field field : fields) { boolean annotationPresent = field.isAnnotationPresent(EncryptField.class); if(annotationPresent){ // 當(dāng)前字段內(nèi)容不為空 if(field.get(userBO) != null){ // 這里對字段內(nèi)容進(jìn)行加密 Object obj = encrypt(field.get(userBO)); // 字段內(nèi)容加密過后 通過反射重新賦給該字段 field.set(userBO, obj); } } } System.out.println(userBO); } public static Object encrypt(Object obj){ return "加密: " + obj; } }
AOP實(shí)戰(zhàn)
切入點(diǎn):
package com.weige.javaskillpoint.controller; import com.weige.javaskillpoint.annotation.Encryption; import com.weige.javaskillpoint.entity.UserBO; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/encrypt") @Slf4j public class EncryptController { @PostMapping("/v1") @Encryption // 切入點(diǎn) public UserBO insert(@RequestBody UserBO user) { log.info("加密后對象:{}", user); return user; } }
切面:
package com.weige.javaskillpoint.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class EncryptAspect { //攔截需加密注解 切入點(diǎn) @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)") public void point() { } @Around("point()") //環(huán)繞通知 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //加密邏輯處理 encrypt(joinPoint); return joinPoint.proceed(); } }
為什么這里要使用AOP:無論是注解,反射,都需要一個啟動方法,我上面演示的是通過main函數(shù)來啟動。使用AOP,項目啟動后,只要調(diào)用切入點(diǎn)對應(yīng)的方法,就會根據(jù)切入點(diǎn)來形成一個切面,進(jìn)行統(tǒng)一的邏輯增強(qiáng);如果大家熟悉SpringMVC,SpringMVC提供了 ResponseBodyAdvice 和 RequestBodyAdvice兩個接口,這兩個接口可以對請求和響應(yīng)進(jìn)行預(yù)處理,就可以不需要使用AOP;
加密解密脫敏實(shí)戰(zhàn)
項目目錄:
pom.xml文件:
<dependencies> <!--Springboot項目自帶 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--Springboot Web項目 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <!-- hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.20</version> </dependency> <!-- 切面 aop --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> </dependencies>
實(shí)體類
用戶信息新增實(shí)體類 :UserBO
package com.weige.javaskillpoint.entity; import com.weige.javaskillpoint.annotation.EncryptField; // 實(shí)體類 public class UserBO { @EncryptField private String name; @EncryptField private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public UserBO(String name, String address) { this.name = name; this.address = address; } @Override public String toString() { return "UserBO{" + "name='" + name + '\'' + ", address='" + address + '\'' + '}'; } }
用戶信息返回實(shí)體類 :UserDO
package com.weige.javaskillpoint.entity; import com.weige.javaskillpoint.annotation.DecryptField; import com.weige.javaskillpoint.enums.DesensitizationEnum; // 實(shí)體類 public class UserDO { @DecryptField(DesensitizationEnum.name) private String name; @DecryptField(DesensitizationEnum.address) private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public UserDO(String name, String address) { this.name = name; this.address = address; } }
脫敏枚舉
package com.weige.javaskillpoint.enums; public enum DesensitizationEnum { name, address, phone; }
注解
解密字段注解(字段):
package com.weige.javaskillpoint.annotation; import com.weige.javaskillpoint.enums.DesensitizationEnum; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface DecryptField { DesensitizationEnum value(); }
解密方法注解(方法 作切入點(diǎn)):
package com.weige.javaskillpoint.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Decryption { }
加密字段注解(字段):
package com.weige.javaskillpoint.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface EncryptField { }
加密方法注解(方法 作切入點(diǎn)):
package com.weige.javaskillpoint.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Encryption { }
控制層
解密 Controller:
package com.weige.javaskillpoint.controller; import com.weige.javaskillpoint.annotation.Decryption; import com.weige.javaskillpoint.entity.UserDO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/decrypt") public class DecryptController { @GetMapping("/v1") @Decryption public UserDO decrypt() { return new UserDO("7c29e296e92893476db5f9477480ba7f", "b5c7ff86ac36c01dda45d9ffb0bf73194b083937349c3901f571d42acdaa7bae"); } }
加密 Controller:
package com.weige.javaskillpoint.controller; import com.weige.javaskillpoint.annotation.Encryption; import com.weige.javaskillpoint.entity.UserBO; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/encrypt") @Slf4j public class EncryptController { @PostMapping("/v1") @Encryption public UserBO insert(@RequestBody UserBO user) { log.info("加密后對象:{}", user); return user; } }
切面
解密脫敏切面:
package com.weige.javaskillpoint.aop; import com.weige.javaskillpoint.annotation.DecryptField; import com.weige.javaskillpoint.enums.DesensitizationEnum; import com.weige.javaskillpoint.utils.AesUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @Slf4j @Aspect @Component public class DecryptAspect { //攔截需解密注解 @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Decryption)") public void point() { } @Around("point()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //解密 return decrypt(joinPoint); } public Object decrypt(ProceedingJoinPoint joinPoint) { Object result = null; try { Object obj = joinPoint.proceed(); if (obj != null) { //拋磚引玉 ,可自行擴(kuò)展其他類型字段的判斷 if (obj instanceof String) { decryptValue(); } else { result = decryptData(obj); } } } catch (Throwable e) { e.printStackTrace(); } return result; } private Object decryptData(Object obj) throws IllegalAccessException { if (Objects.isNull(obj)) { return null; } if (obj instanceof ArrayList) { decryptList(obj); } else { decryptObj(obj); } return obj; } private void decryptObj(Object obj) throws IllegalAccessException { Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { boolean hasSecureField = field.isAnnotationPresent(DecryptField.class); if (hasSecureField) { field.setAccessible(true); if (field.get(obj) != null) { String realValue = (String) field.get(obj); DesensitizationEnum desensitizationEnum = field.getAnnotation(DecryptField.class).value(); String value = (String) AesUtil.decrypt(realValue,desensitizationEnum); field.set(obj, value); } } } } private void decryptList(Object obj) throws IllegalAccessException { List<Object> result = new ArrayList<>(); if (obj instanceof ArrayList) { result.addAll((Collection<?>) obj); } for (Object object : result) { decryptObj(object); } } private void decryptValue() { log.info("根據(jù)對象進(jìn)行解密脫敏,單個字段不做處理!"); } }
加密切面:
package com.weige.javaskillpoint.aop; import com.weige.javaskillpoint.annotation.EncryptField; import com.weige.javaskillpoint.entity.UserBO; import com.weige.javaskillpoint.utils.AesUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import java.lang.reflect.Field; @Slf4j @Aspect @Component public class EncryptAspect { //攔截需加密注解 @Pointcut("@annotation(com.weige.javaskillpoint.annotation.Encryption)") public void point() { } @Around("point()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //加密 encrypt(joinPoint); return joinPoint.proceed(); } public void encrypt(ProceedingJoinPoint joinPoint) { Object[] objects; try { objects = joinPoint.getArgs(); if (objects.length != 0) { for (Object object : objects) { if (object instanceof UserBO) { Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(EncryptField.class)) { field.setAccessible(true); if (field.get(object) != null) { // 進(jìn)行加密 Object o = field.get(object); Object encrypt = AesUtil.encrypt(field.get(object)); field.set(object, encrypt); } } } } } } } catch (Exception e) { log.error(e.getMessage()); } } }
工具類
加密工具類:AesUtil
package com.weige.javaskillpoint.utils; import cn.hutool.core.util.CharsetUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.AES; import com.weige.javaskillpoint.enums.DesensitizationEnum; public class AesUtil { // 默認(rèn)16位 或 128 256位 public static String AES_KEY = "Wk#qerdfdshbd910"; public static AES aes = SecureUtil.aes(AES_KEY.getBytes()); public static Object encrypt(Object obj) { return aes.encryptHex((String) obj); } public static Object decrypt(Object obj, DesensitizationEnum desensitizationEnum) { // 解密 Object decrypt = decrypt(obj); // 脫敏 return DesensitizationUtil.desensitization(decrypt, desensitizationEnum); } public static Object decrypt(Object obj) { return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8); } }
脫敏工具類:DesensitizationUtil
package com.weige.javaskillpoint.utils; import cn.hutool.core.util.StrUtil; import com.weige.javaskillpoint.enums.DesensitizationEnum; public class DesensitizationUtil { public static Object desensitization(Object obj, DesensitizationEnum desensitizationEnum) { Object result; switch (desensitizationEnum) { case name: result = strUtilHide(obj, 1); break; case address: result = strUtilHide(obj, 3); break; default: result = ""; } return result; } /** * start從0開始 */ public static Object strUtilHide(String obj, int start, int end) { return StrUtil.hide(obj, start, end); } public static Object strUtilHide(Object obj, int start) { return strUtilHide(((String) obj), start, ((String) obj).length()); } }
以上代碼不難,大伙復(fù)制到本地跑一遍,基本就能理解;愿每一位程序員少走彎路!
以上就是SpringBoot實(shí)現(xiàn)數(shù)據(jù)加密脫敏的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot數(shù)據(jù)加密脫敏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中使用異步調(diào)度程序的高級方法
本文主要介紹了SpringBoot中使用異步調(diào)度程序的高級方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07一文詳解Java?etcd的應(yīng)用場景及編碼實(shí)戰(zhàn)
etcd?是一個高度一致的分布式鍵值存儲系統(tǒng)。本文旨在幫助大家理解etcd,從宏觀角度俯瞰etcd全局,掌握etcd的基本操作技能,需要的可以參考一下2022-08-08groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類
這篇文章主要為大家介紹了groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03java短信驗(yàn)證碼獲取次數(shù)限制實(shí)例
這篇文章主要介紹了java短信驗(yàn)證碼獲取次數(shù)限制實(shí)例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01java面試常問的Runnable和Callable的區(qū)別
大家好,本篇文章主要講的是java面試常問的Runnable和Callable的區(qū)別,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01