SpringBoot實(shí)現(xiàn)數(shù)據(jù)加密脫敏的示例代碼
SpringBoot 實(shí)現(xiàn)數(shù)據(jù)加密脫敏(注解 + 反射 + AOP)
場(chǎng)景:響應(yīng)政府要求,商業(yè)軟件應(yīng)保證用戶基本信息不被泄露,不能直接展示用戶手機(jī)號(hào),身份證,地址等敏感信息。
根據(jù)上面場(chǎng)景描述,我們可以分析出兩個(gè)點(diǎn)。
- 不被泄露說(shuō)明用戶信息應(yīng)被加密儲(chǔ)存;
- 不能直接展示說(shuō)明用戶信息應(yīng)脫敏展示;
解決方案
傻瓜式編程:將項(xiàng)目中關(guān)于用戶信息實(shí)體類的字段,比如姓名,手機(jī)號(hào),身份證,地址等,在新增進(jìn)數(shù)據(jù)庫(kù)之前,對(duì)數(shù)據(jù)進(jìn)行加密處理;在列表中展示用戶信息時(shí),對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行解密脫敏,然后返回給前端;
切入式編程:將項(xiàng)目中關(guān)于用戶信息實(shí)體類的字段用注解給標(biāo)記,新增用戶信息實(shí)體類(這里我們用UserBO來(lái)表示,給UserBO里面的name,phone字段添加@EncryptField),返回用戶信息實(shí)體類(這里我們用UserDO來(lái)表示,給UserDO里面的name,phone字段添加@DecryptField);然后利用@EncryptField,@DecryptField做為切入點(diǎn),以切面的形式實(shí)現(xiàn)加密,解密脫敏;
傻瓜式編程不是說(shuō)傻,而是相當(dāng)于切入式編程,傻瓜式編程需要對(duì)用戶信息相關(guān)的所有接口進(jìn)行加密,解密脫敏的邏輯處理,這里改動(dòng)的地方就比較多,風(fēng)險(xiǎn)高,重復(fù)操作相同的邏輯,工作量大,后期不好維護(hù);切入式編程只需要對(duì)用戶信息字段添加注解,對(duì)有注解的字段統(tǒng)一進(jìn)行加密,解密脫敏邏輯處理,操作方便,高聚合,易維護(hù);
方案實(shí)現(xiàn)
傻瓜式編程沒(méi)什么難度,這里我給大家有切入式編程來(lái)實(shí)現(xiàn);在實(shí)現(xiàn)之前,跟大家預(yù)熱一下注解,反射,AOP的知識(shí);
注解實(shí)戰(zhàn)
創(chuàng)建注解
創(chuàng)建一個(gè)只能標(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 說(shuō)明該注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 說(shuō)明該注解在運(yùn)行時(shí)生效
public @interface Encryption {
}創(chuàng)建一個(gè)只能標(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 說(shuō)明該注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 說(shuō)明該注解在運(yùn)行時(shí)生效
public @interface EncryptField {
}創(chuàng)建一個(gè)標(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ī)號(hào)脫敏
}創(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 {
// 生成并初始化對(duì)象
UserDO userDO = new UserDO("夢(mèng)想是什么","湖北省武漢市");
// 反射獲取當(dāng)前對(duì)象的所有字段
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();
// 控制臺(tái)輸出
System.out.println(desensitizationEnum);
// 根據(jù)不同的value值 我們可以對(duì)字段進(jìn)行不同邏輯的脫敏 比如姓名脫敏-魏*,手機(jī)號(hào)脫敏-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){
// 這里對(duì)字段內(nèi)容進(jìn)行加密
Object obj = encrypt(field.get(userBO));
// 字段內(nèi)容加密過(guò)后 通過(guò)反射重新賦給該字段
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("加密后對(duì)象:{}", 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:無(wú)論是注解,反射,都需要一個(gè)啟動(dòng)方法,我上面演示的是通過(guò)main函數(shù)來(lái)啟動(dòng)。使用AOP,項(xiàng)目啟動(dòng)后,只要調(diào)用切入點(diǎn)對(duì)應(yīng)的方法,就會(huì)根據(jù)切入點(diǎn)來(lái)形成一個(gè)切面,進(jìn)行統(tǒng)一的邏輯增強(qiáng);如果大家熟悉SpringMVC,SpringMVC提供了 ResponseBodyAdvice 和 RequestBodyAdvice兩個(gè)接口,這兩個(gè)接口可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行預(yù)處理,就可以不需要使用AOP;
加密解密脫敏實(shí)戰(zhàn)
項(xiàng)目目錄:

pom.xml文件:
<dependencies>
<!--Springboot項(xiàng)目自帶 -->
<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項(xiàng)目 -->
<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("加密后對(duì)象:{}", 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ù)對(duì)象進(jìn)行解密脫敏,單個(gè)字段不做處理!");
}
}加密切面:
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開(kāi)始
*/
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ù)加密脫敏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中使用異步調(diào)度程序的高級(jí)方法
本文主要介紹了SpringBoot中使用異步調(diào)度程序的高級(jí)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
一文詳解Java?etcd的應(yīng)用場(chǎng)景及編碼實(shí)戰(zhàn)
etcd?是一個(gè)高度一致的分布式鍵值存儲(chǔ)系統(tǒng)。本文旨在幫助大家理解etcd,從宏觀角度俯瞰etcd全局,掌握etcd的基本操作技能,需要的可以參考一下2022-08-08
groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類
這篇文章主要為大家介紹了groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
java短信驗(yàn)證碼獲取次數(shù)限制實(shí)例
這篇文章主要介紹了java短信驗(yàn)證碼獲取次數(shù)限制實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
java面試常問(wèn)的Runnable和Callable的區(qū)別
大家好,本篇文章主要講的是java面試常問(wèn)的Runnable和Callable的區(qū)別,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01

