SpringBoot?自定義注解之脫敏注解詳解
自定義注解之脫敏注解
數(shù)據(jù)脫敏是指對(duì)某些敏感信息通過(guò)脫敏規(guī)則進(jìn)行數(shù)據(jù)的變形,實(shí)現(xiàn)敏感隱私數(shù)據(jù)的可靠保護(hù)。需求是把返回到前端的數(shù)據(jù)進(jìn)行脫敏,以免造成隱私信息的泄露。
一、脫敏后的效果

這樣顯示很不好吧,所有信息都泄露了

這樣就很好了吧
二、代碼
1.脫敏注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {
/**
* 脫敏數(shù)據(jù)類型
*/
SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;
/**
* 前置不需要打碼的長(zhǎng)度
*/
int prefixNoMaskLen() default 0;
/**
* 后置不需要打碼的長(zhǎng)度
*/
int suffixNoMaskLen() default 0;
/**
* 用什么打碼
*/
String symbol() default "*";
}
2.定義脫敏類型
public enum SensitiveTypeEnum {
/**
* 自定義
*/
CUSTOMER,
/**
* 姓名
*/
NAME,
/**
* 身份證
*/
ID_NUM,
/**
* 手機(jī)號(hào)碼
*/
PHONE_NUM
}
3.敏感工具類
public class DesensitizedUtils {
/**
* 對(duì)字符串進(jìn)行脫敏操作
*
* @param origin 原始字符串
* @param prefixNoMaskLen 左側(cè)需要保留幾位明文字段
* @param suffixNoMaskLen 右側(cè)需要保留幾位明文字段
* @param maskStr 用于遮罩的字符串, 如'*'
* @return 脫敏后結(jié)果
*/
public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
if (origin == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0, n = origin.length(); i < n; i++) {
if (i < prefixNoMaskLen) {
sb.append(origin.charAt(i));
continue;
}
if (i > (n - suffixNoMaskLen - 1)) {
sb.append(origin.charAt(i));
continue;
}
sb.append(maskStr);
}
return sb.toString();
}
/**
* 【中文姓名】只顯示最后一個(gè)漢字,其他隱藏為星號(hào),比如:**夢(mèng)
*
* @param fullName 姓名
* @return 結(jié)果
*/
public static String chineseName(String fullName) {
if (fullName == null) {
return null;
}
return desValue(fullName, 1, 0, "*");
}
/**
* 【身份證號(hào)】顯示前4位, 后2位,其他隱藏。
*
* @param id 身份證號(hào)碼
* @return 結(jié)果
*/
public static String idCardNum(String id) {
return desValue(id, 4, 2, "*");
}
/**
* 【手機(jī)號(hào)碼】前三位,后四位,其他隱藏。
*
* @param num 手機(jī)號(hào)碼
* @return 結(jié)果
*/
public static String mobilePhone(String num) {
return desValue(num, 3, 4, "*");
}
}
4.脫敏序列化信息
@NoArgsConstructor
@AllArgsConstructor
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
/**
* 脫敏類型
*/
private SensitiveTypeEnum sensitiveTypeEnum;
/**
* 前幾位不脫敏
*/
private Integer prefixNoMaskLen;
/**
* 最后幾位不脫敏
*/
private Integer suffixNoMaskLen;
/**
* 用什么打碼
*/
private String symbol;
@Override
public void serialize(final String origin, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException {
switch (sensitiveTypeEnum) {
case CUSTOMER:
jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
break;
case NAME:
jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));
break;
case ID_NUM:
jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));
break;
case PHONE_NUM:
jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));
break;
default:
throw new IllegalArgumentException("unknown sensitive type enum " + sensitiveTypeEnum);
}
}
@Override
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
final BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(),
sensitive.suffixNoMaskLen(), sensitive.symbol());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
小結(jié)一下
該注解用于隱私數(shù)據(jù)的脫敏,只作用于類的屬性上。該注解有四個(gè)屬性,type表示脫敏數(shù)據(jù)類型(默認(rèn)為CUSTOMER自定義,后面三個(gè)屬性才有效),prefixNoMaskLen表示前置不需要打碼的長(zhǎng)度(默認(rèn)為0),suffixNoMaskLen表示后置不需要打碼的長(zhǎng)度(默認(rèn)為0),symbol表示用什么打碼(默認(rèn)為*)。
一般用于返回對(duì)象給前端對(duì)象中包含隱私數(shù)據(jù)例如身份證、詳細(xì)地址需要進(jìn)行脫敏的情況。
示例:
public class UserInfo {
@Sensitive(type = SensitiveTypeEnum.NAME)
private String name;
@Sensitive(type = SensitiveTypeEnum.ID_NUM)
private String idNum;
@Sensitive(type = SensitiveTypeEnum.PHONE_NUM)
private String phone;
@Sensitive(type = SensitiveTypeEnum.CUSTOMER, prefixNoMaskLen = 3, suffixNoMaskLen = 2, symbol = "#")
private String address;
@Sensitive(prefixNoMaskLen = 1, suffixNoMaskLen = 2, symbol = "*")
private String password;
}
如果還有疑問(wèn)我寫(xiě)了個(gè)demo,可以下載下來(lái)運(yùn)行看看
鏈接: 脫敏注解demo.
自己手寫(xiě)的一個(gè)高效自定義字符串脫敏注解
經(jīng)理要求寫(xiě)一個(gè)自定義脫敏注解,百度查了一堆。都是效率比較低的
自己寫(xiě)了個(gè) 僅供參考
/**
* description: 數(shù)據(jù)脫敏
* 1、默認(rèn)不傳部位、不傳顯示*號(hào)數(shù)量時(shí)字段全部脫敏
*
* 原始字符串 adminis 總長(zhǎng)度從0計(jì)算 總數(shù)6
* index=(0,2) size = 1 下標(biāo)即從0到2以內(nèi)的字符標(biāo)注“ * ”,size=1 則只填充一個(gè)* size 不能超過(guò)截取字符
* index=(2,3) size =2 下標(biāo)即從2到3以內(nèi)的字符標(biāo)注“ * ”,size=2 則只填充二個(gè)* size 不能超過(guò)截取字符
*
* date: 2020/3/13 15:56
*
* @author oakdog
* @version 1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = Desensitization.ConvertDesensitization.class)
public @interface Desensitization {
/**
* 傳入的下標(biāo)索引
* 規(guī)則 第一位起始下標(biāo) 第二位是結(jié)束下標(biāo) 默認(rèn)值6位下標(biāo)
**/
int[] index() default {0,6};
/**
* 需要脫敏的字符長(zhǎng)度
* 規(guī)則 輸入 3 :則根據(jù)index下標(biāo)索引對(duì)應(yīng)脫敏3個(gè)字符 默認(rèn)6個(gè)長(zhǎng)度脫敏
**/
int size() default 6;
class ConvertDesensitization extends StdSerializer<Object> implements ContextualSerializer {
private int[] index;
private int size;
public ConvertDesensitization() {
super(Object.class);
}
private ConvertDesensitization(int[] index,int size) {
super(Object.class);
this.size = size;
this.index = index;
}
@Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
char[] str = value.toString().toCharArray();
StringBuilder builder = new StringBuilder();
String char1 = (String) value;
if(str.length > 0) {
//字符長(zhǎng)度超長(zhǎng)處理
if(index[0] < str.length && index[1] < str.length) {
//使用默認(rèn)初始值的脫敏處理
if(index[0] == 0) {
//如果輸入脫敏大小長(zhǎng)度小于0或大于原始脫敏字符長(zhǎng)度,則全脫敏字符
if (size < 0 || size < str.length) {
char[] charStr = char1.substring(index[1], str.length).toCharArray();
char[] charStr1 = char1.substring(index[0], index[1]).toCharArray();
builder.append(charStr1);
for (int i = 0; i < charStr.length; i++) {
if(size > i) {
builder.append("*");
}else {
builder.append(charStr[i]);
}
}
}else {
builder.append(getDefaultChar((String) value,"left"));
}
}else {
//從中間位置截取脫敏處理
//如果輸入脫敏大小長(zhǎng)度小于0或大于原始脫敏字符長(zhǎng)度,則全脫敏字符
if (size < 0 || size < str.length) {
char[] charStr = char1.substring(index[0], str.length - index[1] + 1).toCharArray(); //2 6-4 2 //中間截取部分
List<Integer> prefix = getPrefix(index[0], (String) value);
//List<Integer> suffix = getSuffix(index[0],index[1], (String) value);
for (Integer integer : prefix) {
builder.append(str[integer]);
}
for (int i = 0; i < charStr.length; i++) {
if (size > i) {
builder.append("*");
} else {
builder.append(charStr[i]);
}
}
char[] chars = Arrays.copyOfRange(str, index[1], str.length);
builder.append(String.valueOf(chars));
}else {
builder.append(getDefaultChar((String) value,"right"));
}
}
}else {
//默認(rèn)處理
builder.append(getDefaultChar((String) value,""));
}
}
jgen.writeString(builder.toString());
}
/**
* 默認(rèn)的填充方式
* @param str 原始字符串
* @param position 位置
* @return
*/
String getDefaultChar(String str,String position){
char[] desensitizationStr = str.toCharArray();
for(int i=0;i<desensitizationStr.length;i++){
if("left".equals(position)){
if(i != 0){
desensitizationStr[i] = '*';
}
}else if("right".equals(position)){
if(i != desensitizationStr.length-1){
desensitizationStr[i] = '*';
}
}else {
if(i != 0 && i != desensitizationStr.length-1){
desensitizationStr[i] = '*';
}
}
}
return String.valueOf(desensitizationStr);
}
/**
* 獲取字符前綴下標(biāo)
* @param index 下標(biāo)
* @param val 原始字符串
* @return
*/
List<Integer> getPrefix(int index,String val){
//int[] chars = {};
List<Integer> listIndex = new ArrayList<>();
for(int i=0;i<val.length();i++){
if(i != index){ //0 1 != 2
listIndex.add(i);
continue;
}
break;
}
return listIndex;
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
int[] index = {0,6}; //初始值
int size = 6; //初始值
Desensitization ann = null;
if (property != null) {
ann = property.getAnnotation(Desensitization.class);
}
if (ann != null) {
index = ann.index();
size = ann.size();
}
return new Desensitization.ConvertDesensitization(index,size);
}
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺談java object對(duì)象在heap中的結(jié)構(gòu)
本文主要介紹了淺談java object對(duì)象在heap中的結(jié)構(gòu),感興趣的同學(xué),可以參考下。2021-06-06
Java微信公眾平臺(tái)開(kāi)發(fā)(14) 微信web開(kāi)發(fā)者工具使用
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開(kāi)發(fā)第十四步,微信web開(kāi)發(fā)者工具的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Java實(shí)現(xiàn)折半插入排序算法的示例代碼
折半插入排序(Binary Insertion Sort)是對(duì)插入排序算法的一種改進(jìn)。不斷的依次將元素插入前面已排好序的序列中。本文將利用Java語(yǔ)言實(shí)現(xiàn)這一排序算法,需要的可以參考一下2022-08-08
使用socket進(jìn)行服務(wù)端與客戶端傳文件的方法
這篇文章主要介紹了使用socket進(jìn)行服務(wù)端與客戶端傳文件的方法,需要的朋友可以參考下2017-08-08
網(wǎng)關(guān)Spring Cloud Gateway HTTP超時(shí)配置問(wèn)題
這篇文章主要介紹了網(wǎng)關(guān)Spring Cloud Gateway HTTP超時(shí)配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01

