SpringBoot對敏感數(shù)據(jù)進行脫敏的全過程
引言
在項目開發(fā)中,數(shù)據(jù)庫經(jīng)常會存儲大量用戶敏感信息,例如手機號、身份證號、郵箱、銀行卡號、家庭地址、姓名等數(shù)據(jù)。如果后端直接把原始數(shù)據(jù)返回給前端,一旦接口被抓包、日志泄露、數(shù)據(jù)庫泄露,就會造成嚴(yán)重的用戶隱私泄露問題,違反數(shù)據(jù)安全規(guī)范。
因此企業(yè)項目普遍需要做數(shù)據(jù)脫敏,也就是在不修改原始數(shù)據(jù)庫數(shù)據(jù)的前提下,對返回字段進行掩碼處理,隱藏部分字符,只展示明文部分。
一、脫敏方案對比
目前常見的數(shù)據(jù)脫敏實現(xiàn)方式有 4 種,各有優(yōu)劣:
1. 工具類手動脫敏
業(yè)務(wù)代碼中手動調(diào)用工具處理,侵入性極強,每個字段都要處理,冗余代碼多。
2. AOP切面脫敏
攔截返回結(jié)果統(tǒng)一處理,缺點是解析整個返回體 JSON,性能較差,復(fù)雜對象嵌套難以精準(zhǔn)控制。
3. MyBatis 層脫敏
SQL查詢、結(jié)果集映射時處理,只針對數(shù)據(jù)庫查詢有效,其他接口返回?zé)o法統(tǒng)一處理。
4. Jackson 序列化層脫敏(本文方案)
最優(yōu)方案。基于 Jackson 自定義序列化器,在實體類字段上加注解即可生效,僅在對象轉(zhuǎn) JSON 階段脫敏,業(yè)務(wù)零侵入、性能高、靈活可控、支持嵌套對象、全局統(tǒng)一,也是目前企業(yè)項目主流標(biāo)準(zhǔn)方案。
二、脫敏核心原理
SpringBoot 默認(rèn)使用 Jackson 完成 Java 對象轉(zhuǎn) JSON 字符串返回前端。
我們自定義 JsonSerializer 序列化器,實現(xiàn)自定義脫敏邏輯,再封裝通用注解標(biāo)注在實體類敏感字段上。
流程:
1. 后端查詢數(shù)據(jù)庫,得到完整原始實體對象
2. 接口返回對象,Jackson 開始序列化
3. 檢測字段上的脫敏注解
4. 執(zhí)行對應(yīng)脫敏序列化器,對數(shù)據(jù)進行掩碼替換
5. 前端最終拿到脫敏后的 JSON 數(shù)據(jù),原始數(shù)據(jù)全程不暴露
優(yōu)勢:原始數(shù)據(jù)庫數(shù)據(jù)完全不變,僅返回時脫敏,不影響入庫、不影響業(yè)務(wù)邏輯。
三、環(huán)境準(zhǔn)備
1. Maven 依賴
SpringBoot Web 自帶 Jackson,無需額外引入依賴,僅需基礎(chǔ) Web、Lombok 即可。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-desensitization</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-desensitization</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Web 核心 內(nèi)置Jackson序列化 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>2. application.yml
無需額外復(fù)雜配置,開箱即用
server: port: 8080
四、統(tǒng)一返回結(jié)果類
沿用本專欄所有文章統(tǒng)一 Result 返回體,格式完全一致。
import lombok.Data;
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg(msg);
return result;
}
}五、脫敏類型枚舉
統(tǒng)一定義所有常用脫敏類型,擴展方便,規(guī)范統(tǒng)一。
包含:手機號、身份證、郵箱、銀行卡、姓名、地址。
/**
* 敏感數(shù)據(jù)脫敏類型枚舉
*/
public enum DesensitizeType {
/**
* 默認(rèn)不處理
*/
DEFAULT,
/**
* 手機號 11位
* 138****1234
*/
PHONE,
/**
* 18位身份證
* 320123********1234
*/
ID_CARD,
/**
* 郵箱
* test****@qq.com
*/
EMAIL,
/**
* 銀行卡號
* 6222****1234
*/
BANK_CARD,
/**
* 中文姓名
* 張**
*/
NAME,
/**
* 詳細(xì)地址
* 廣東省深圳市南山區(qū)****
*/
ADDRESS
}六、脫敏工具類
封裝所有類型脫敏算法,抽取公共工具方法,統(tǒng)一掩碼替換規(guī)則。
import org.springframework.util.StringUtils;
/**
* 敏感數(shù)據(jù)脫敏工具類
*/
public class DesensitizeUtil {
/**
* 通用掩碼截取方法
* @param str 原始字符串
* @param start 前面保留位數(shù)
* @param end 后面保留位數(shù)
* @return 脫敏后字符串
*/
public static String mask(String str, int start, int end) {
if (StringUtils.isEmpty(str)) {
return str;
}
int len = str.length();
// 長度不足直接返回原數(shù)據(jù)
if (len <= start + end) {
return str;
}
// 截取前綴 + 中間星號 + 截取后綴
String prefix = str.substring(0, start);
String suffix = str.substring(len - end);
return prefix + "****" + suffix;
}
// ==================== 各類敏感數(shù)據(jù)脫敏實現(xiàn) ====================
/**
* 手機號脫敏 11位
*/
public static String phone(String phone) {
return mask(phone, 3, 4);
}
/**
* 身份證18位脫敏
*/
public static String idCard(String idCard) {
return mask(idCard, 6, 4);
}
/**
* 郵箱脫敏
*/
public static String email(String email) {
if (StringUtils.isEmpty(email) || !email.contains("@")) {
return email;
}
int index = email.indexOf("@");
String prefix = email.substring(0, index);
String suffix = email.substring(index);
// 用戶名部分脫敏
String pre = prefix.length() > 2 ? prefix.substring(0, 2) : prefix;
return pre + "****" + suffix;
}
/**
* 銀行卡脫敏
*/
public static String bankCard(String card) {
return mask(card, 4, 4);
}
/**
* 中文姓名脫敏
*/
public static String name(String name) {
if (StringUtils.isEmpty(name) || name.length() <= 1) {
return name;
}
return name.charAt(0) + "**";
}
/**
* 詳細(xì)地址脫敏
*/
public static String address(String address) {
if (StringUtils.isEmpty(address) || address.length() <= 8) {
return address;
}
return address.substring(0, 8) + "****";
}
}七、自定義脫敏注解
封裝通用注解,標(biāo)注在實體類字段上,指定脫敏類型,無侵入式。
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*;
/**
* 自定義敏感字段脫敏注解
* 標(biāo)注在實體類字段上,序列化自動脫敏
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeSerializer.class)
public @interface Desensitize {
/**
* 脫敏類型
*/
DesensitizeType value();
}八、自定義Jackson序列化器
實現(xiàn) JsonSerializer,在序列化階段根據(jù)注解類型,調(diào)用對應(yīng)脫敏方法。
整個方案核心代碼,Spring 會自動在對象轉(zhuǎn)JSON時執(zhí)行。
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import java.io.IOException;
/**
* 自定義脫敏序列化器
* Jackson 序列化層統(tǒng)一處理
*/
public class DesensitizeSerializer extends StdScalarSerializer<String> {
public DesensitizeSerializer() {
super(String.class);
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 獲取字段上的脫敏注解
Desensitize annotation = gen.getAnnotation(Desensitize.class);
if (annotation == null) {
gen.writeString(value);
return;
}
DesensitizeType type = annotation.value();
String result = value;
// 根據(jù)類型執(zhí)行對應(yīng)脫敏算法
switch (type) {
case PHONE:
result = DesensitizeUtil.phone(value);
break;
case ID_CARD:
result = DesensitizeUtil.idCard(value);
break;
case EMAIL:
result = DesensitizeUtil.email(value);
break;
case BANK_CARD:
result = DesensitizeUtil.bankCard(value);
break;
case NAME:
result = DesensitizeUtil.name(value);
break;
case ADDRESS:
result = DesensitizeUtil.address(value);
break;
default:
break;
}
// 寫入脫敏后數(shù)據(jù)
gen.writeString(result);
}
}九、用戶實體類測試
直接在敏感字段加上 @Desensitize(xxx) 注解即可,無需修改任何業(yè)務(wù)代碼。
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String username;
// 姓名脫敏
@Desensitize(DesensitizeType.NAME)
private String realName;
// 手機號脫敏
@Desensitize(DesensitizeType.PHONE)
private String phone;
// 身份證脫敏
@Desensitize(DesensitizeType.ID_CARD)
private String idCard;
// 郵箱脫敏
@Desensitize(DesensitizeType.EMAIL)
private String email;
// 銀行卡脫敏
@Desensitize(DesensitizeType.BANK_CARD)
private String bankCard;
// 詳細(xì)地址脫敏
@Desensitize(DesensitizeType.ADDRESS)
private String address;
}十、測試接口
模擬數(shù)據(jù)庫查詢完整數(shù)據(jù),直接返回對象,由 Jackson 序列化自動完成脫敏。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/info")
public Result<User> getUserInfo() {
// 模擬數(shù)據(jù)庫完整原始敏感數(shù)據(jù)
User user = new User();
user.setId(1L);
user.setUsername("admin");
user.setRealName("張三三");
user.setPhone("13812345678");
user.setIdCard("320123199801011234");
user.setEmail("zhangsan@163.com");
user.setBankCard("6222021234567891234");
user.setAddress("廣東省深圳市南山區(qū)科技園高新園區(qū)1棟");
return Result.success(user);
}
}十一、運行測試效果
啟動項目訪問接口:
http://localhost:8080/user/info
前端返回 JSON 全部自動脫敏,效果如下:
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"username": "admin",
"realName": "張**",
"phone": "138****5678",
"idCard": "320123********1234",
"email": "zh****@163.com",
"bankCard": "6222****1234",
"address": "廣東省深圳市南山區(qū)****"
}
}數(shù)據(jù)庫原始數(shù)據(jù)完全不變,僅接口返回序列化階段脫敏。
十二、擴展增強:嵌套對象脫敏
該方案天然支持實體類嵌套對象脫敏,內(nèi)部字段注解依然生效,無需額外配置。
@Data
class UserInfo {
@Desensitize(DesensitizeType.PHONE)
private String phone;
}十三、生產(chǎn)環(huán)境擴展優(yōu)化
1. 動態(tài)自定義脫敏規(guī)則
支持注解自定義前后保留位數(shù),無需新增枚舉,靈活適配各種不規(guī)則字段。
@Desensitize(type = CUSTOM, start = 2, end = 3)
2. 全局配置區(qū)分環(huán)境脫敏
開發(fā)、測試環(huán)境不脫敏(方便調(diào)試),生產(chǎn)環(huán)境自動脫敏,通過配置文件開關(guān)控制。
desensitize: enable: true # 全局脫敏總開關(guān)
3. 排除指定接口脫敏
部分后臺管理接口、管理員接口需要查看完整原始數(shù)據(jù),配置放行不脫敏。
4. 結(jié)合日志脫敏
擴展實現(xiàn)日志打印脫敏,防止日志打印泄露用戶隱私。
5. 空值、null值兼容處理
完善序列化器空值判斷,避免空指針異常。
十四、方案優(yōu)勢總結(jié)
1. 業(yè)務(wù)零侵入:僅實體字段加注解,Controller、Service、DAO 全部無需改動
2. 性能極高:基于 Jackson 原生序列化,無額外反射、無JSON二次解析
3. 統(tǒng)一可控:所有接口返回統(tǒng)一處理,一處配置全局生效
4. 擴展方便:新增脫敏類型只需要加枚舉+工具方法,無需改動核心序列化器
5. 兼容所有返回:單對象、List集合、嵌套對象、Result包裝體全部支持
6. 數(shù)據(jù)安全:原始庫數(shù)據(jù)不變,僅對外返回掩碼數(shù)據(jù),滿足等保合規(guī)要求
十五、常見問題
1. 注解不生效
原因:字段是 static 修飾、字段無get方法、Lombok版本過低;
解決:去掉static,保證Lombok正常生成getter。
2. 返回null不脫敏
工具類已做空值判斷,直接返回null,不會報錯。
3. 集合、List返回不生效
Jackson 序列化天然支持集合遍歷序列化,完全生效無需額外處理。
4. 自定義序列化與全局Jackson配置沖突
使用 @JacksonAnnotationsInside 注解整合,不會沖突。
以上就是SpringBoot對敏感數(shù)據(jù)進行脫敏的全過程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot敏感數(shù)據(jù)進行脫敏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解java代碼中init method和destroy method的三種使用方式
這篇文章主要介紹了詳解java代碼中init method和destroy method的三種使用方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
MyBatis-Plus批量操作SQL日志不打印問題的解決方案
在使用 MyBatis-Plus 的?saveBatch()?和?updateBatchById()?方法進行批量數(shù)據(jù)操作時,發(fā)現(xiàn)自定義的 Druid SQL 日志攔截器無法打印這些批量操作的 SQL 語句,導(dǎo)致調(diào)試和問題排查困難,本文給大家該問題的詳細(xì)解決方案,需要的朋友可以參考下2026-03-03
springboot @RequestBody 接收字符串實例
這篇文章主要介紹了springboot @RequestBody 接收字符串實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java7和Java8中的ConcurrentHashMap原理解析
這篇文章主要介紹了Java7和Java8中的ConcurrentHashMap原理解析,對ConcurrentHashMap感興趣的讀者,一定要好好看一下2021-04-04
下載遠(yuǎn)程maven倉庫的jar?手動放到本地倉庫詳細(xì)操作
這篇文章主要介紹了如何下載遠(yuǎn)程maven倉庫的jar?手動放到本地倉庫,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
使用curator實現(xiàn)zookeeper鎖服務(wù)的示例分享
這篇文章主要介紹了使用curator實現(xiàn)zookeeper鎖服務(wù)的示例,需要的朋友可以參考下2014-02-02

