我利用了AOP和标签的原理来进行组合,实现脱敏的效果,不足之处就是在于如果查询条数多了会有轻微卡顿,使用分页时完全看不出来卡顿。
1@Documented —— 指明拥有这个注解的元素可以被javadoc此类的工具文档化。这种类型应该用于注解那些影响客户使用带注释的元素声明的类型。如果一种声明使用Documented进行注解,这种类型的注解被作为被标注的程序成员的公共API。
2@Target——指明该类型的注解可以注解的程序元素的范围。该元注解的取值可以为TYPE,METHOD,CONSTRUCTOR,FIELD等。如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。
3@Inherited——指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。
4@Retention——指明了该Annotation被保留的时间长短。RetentionPolicy取值为SOURCE,CLASS,RUNTIME。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 属性标签,配合AOP使用,单独使用无效
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ReadableSensitiveVerify {
// 枚举的值
ReadableSensitiveTypeEnum value();
}
建好标签之后我们需要为需要脱敏的字段创建相应的枚举
枚举是一种特殊的类,特殊在它的对象是有限的几个常量对象。它既是一种类(class)类型却又比类类型多了些特殊的约束,枚举的本质是一种受限制的类。
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ReadableSensitiveTypeEnum {
/**
* 身份证编号
*/
ID_CARD,
/**
* 地址/住址
*/
ADDRESS,
/**
* 姓名
*/
NAME,
/**
* 手机号
*/
PHONE,
/**
* 手机号
*/
EMAIL,
/**
* 银行卡号
*/
BANK_CARD_NO;
}
枚举创建完成之后我们就可以在实体属性上添加标签了,等AOP创建完成后有标签的属性就可以实现脱敏的效果了。
/**
* 证件号码
*/
@TableField("opIDCode")
@ReadableSensitiveVerify(ReadableSensitiveTypeEnum.ID_CARD)
private String opIDCode;
package com.lyxcn.witcity.service.village.Aspect;
import com.lyxcn.smartcity.yq.commons.config.security.SecurityConfig.LoginConfig.SecurityUtils;
import com.lyxcn.witcity.service.village.utils.desensitization.DesensitizationUtils;
import com.lyxcn.witcity.service.village.utils.desensitization.ReadableSensitiveTypeEnum;
import com.lyxcn.witcity.service.village.utils.desensitization.ReadableSensitiveVerify;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* DAO切面,插入创建人,创建时间,修改人,修改时间
* @Auther LIUXIAO
* @Date 2019/10/14
*/
@Aspect
@Component
@Configuration
public class DaoAspectCommon {
/**
* @Author lx
* @Description findPage分页脱敏
* @Date 2022/6/22 17:45
* @Param []
* @return void
*/
@Pointcut("execution(* com.lyxcn.witcity.service.village.mapper.*.*.findPage(..))")
public void findPagePointCut() {
}
/**
* @Author lx
* @Description info详情脱敏
* @Date 2022/6/22 17:45
* @Param []
* @return void
*/
@Pointcut("execution(* com.lyxcn.witcity.service.village.mapper.*.*.info(..))")
public void infoPointCut() {
}
/**
* @Author lx
* @Description findPage分页脱敏
* @Date 2022/6/22 17:45
* @Param []
* @return void
*/
@AfterReturning(value = "findPagePointCut()",returning = "methodResult")
public Object findPagePointCut(JoinPoint proceedingJoinPoint, Object methodResult) throws Throwable {
// 获取当前访问权限的权限码
List<String> module = null;
try {
module = SecurityUtils.getLoginInfo().getModule();
} catch (Exception e) {
return methodResult;
}
// 在权限码中找到脱敏的权限码并填充到list中
List result =module.stream().filter(x -> x.contains("ROLE_display")).collect(Collectors.toList());
int phone = 0,IDCode = 0, skip = 0, noSkip = 0;
// 判定电话
if (result.contains("ROLE_display.IDCode")){
if (result.contains("ROLE_displayHalf.IDCode")){
phone = 2;
}
phone = 1;
}
if (result.contains("ROLE_displayHalf.IDCode")){
phone = 2;
}
// 判定身份证
if (result.contains("ROLE_display.Phone")){
if (result.contains("ROLE_displayHalf.Phone")){
IDCode = 2;
}
IDCode = 1;
}
if (result.contains("ROLE_displayHalf.Phone")){
IDCode = 2;
}
//获取执行方法的参数
if (Objects.isNull(methodResult)) {
return null;
}
List list = null;
try {
list = (List)methodResult;
} catch (Exception e) {
}
for (Object map:list){
Field[] fields = map.getClass().getDeclaredFields();
for (Field field :fields){
field.setAccessible(true);
// 方法参数中属性的指定注解
ReadableSensitiveVerify annotation = field.getAnnotation(ReadableSensitiveVerify.class);
noSkip++;
try {
if (Objects.nonNull(annotation)) {
if (Objects.nonNull(field)){
ReadableSensitiveTypeEnum typeEnum = annotation.value();
String valueStr = field.getName();
// 利用BeanUtils.getProperty()取出参数
String property = BeanUtils.getProperty(map, valueStr);
switch (typeEnum) {
case PHONE:
if (phone == 2){
// 脱敏工具类脱敏
property = DesensitizationUtils.desensitizedPhoneNumber(property);
}
if (phone == 0){
property = null;
}
break;
case ID_CARD:
if (IDCode == 2){
property = DesensitizationUtils.desensitizedIdNumber(property);
}
if (IDCode == 0){
property = null;
}
break;
default:
}
// 利用BeanUtils.setProperty() 设置脱敏后的参数
BeanUtils.setProperty(map ,valueStr, property);
}
}else{
skip++;
}
}catch (Exception e){
}
}
if (noSkip == skip){
return methodResult;
}else{
skip = 0;
noSkip = 0;
}
}
return methodResult;
}
/**
* @Author lx
* @Description info详情脱敏
* @Date 2022/6/22 17:45
* @Param []
* @return void
*/
@AfterReturning(value = "infoPointCut()",returning = "methodResult")
public Object infoPointCut(JoinPoint proceedingJoinPoint, Object methodResult) throws Throwable {
List<String> module = null;
try {
module = SecurityUtils.getLoginInfo().getModule();
} catch (Exception e) {
e.printStackTrace();
return methodResult;
}
List result =module.stream().filter(x -> x.contains("ROLE_display")).collect(Collectors.toList());
int phone = 0,IDCode = 0;
// 判定电话
if (result.contains("ROLE_display.IDCode")){
if (result.contains("ROLE_displayHalf.IDCode")){
phone = 2;
}
phone = 1;
}
if (result.contains("ROLE_displayHalf.IDCode")){
phone = 2;
}
// 判定身份证
if (result.contains("ROLE_display.Phone")){
if (result.contains("ROLE_displayHalf.Phone")){
IDCode = 2;
}
IDCode = 1;
}
if (result.contains("ROLE_displayHalf.Phone")){
IDCode = 2;
}
//获取执行方法的参数
Field[] fields = new Field[0];
try {
fields = methodResult.getClass().getDeclaredFields();
} catch (Exception e) {
// e.printStackTrace();
}
for (Field field :fields){
field.setAccessible(true);
//方法参数中属性的指定注解
ReadableSensitiveVerify annotation = field.getAnnotation(ReadableSensitiveVerify.class);
try {
if (Objects.nonNull(annotation)) {
if (Objects.nonNull(field)){
ReadableSensitiveTypeEnum typeEnum = annotation.value();
String valueStr = field.getName();
String property = BeanUtils.getProperty(methodResult, valueStr);
switch (typeEnum) {
case PHONE:
if (phone == 2){
property = DesensitizationUtils.desensitizedPhoneNumber(property);
}
if (phone == 0){
property = null;
}
break;
case ID_CARD:
if (IDCode == 2){
property = DesensitizationUtils.desensitizedIdNumber(property);
}
if (IDCode == 0){
property = null;
}
break;
default:
}
BeanUtils.setProperty(methodResult,valueStr, property);
}
}
}catch (Exception e){
}
}
return methodResult;
}
}
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
这里的脱敏就有用到了正则匹配,通过上面的标签来匹配相应字段进行相应的脱敏处理。
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DesensitizationUtils {
/**
* @description: 名字脱敏
* 脱敏规则: 隐藏中中间部分,比如:李某人 置换为 李*人 , 李某置换为 *某,司徒司翘置换为 司**翘
* @return:
* @author: lx
* @time: 2022/6/22
*/
public static String desensitizedName(String fullName){
if (!Strings.isNullOrEmpty(fullName)) {
int length = fullName.length();
if(length == 2){
return "*".concat(fullName.substring(1));
}else if(length == 3){
return StringUtils.left(fullName,1).concat("*").concat(StringUtils.right(fullName,1));
}else if(length > 3){
return StringUtils.left(fullName,1).concat(generateAsterisk(fullName.substring(1,length-1).length())).concat(StringUtils.right(fullName,1));
}else {
return fullName;
}
}
return fullName;
}
/**
* @description: 手机号脱敏,脱敏规则: 保留前三后四, 比如15566026528置换为155****6528
* @return:
* @author: lx
* @time: 2022/6/22
*/
public static String desensitizedPhoneNumber(String phoneNumber){
if(StringUtils.isNotEmpty(phoneNumber)){
int length = phoneNumber.length();
if(length == 11){
return phoneNumber.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");
}else if(length > 2){
return StringUtils.left(phoneNumber,1).concat(generateAsterisk(phoneNumber.substring(1,length-2).length())).concat(StringUtils.right(phoneNumber,1));
}else {
return phoneNumber;
}
}
return phoneNumber;
}
/**
* @description: 身份证脱敏
* 脱敏规则: 保留前六后三, 适用于15位和18位身份证号:
* 原身份证号(15位):210122198401187,脱敏后的身份证号:210122******187
* 原身份证号(18位):210122198401187672,脱敏后的身份证号:210122*********672
* @return:
* @author: lx
* @time: 2022/6/22
*/
public static String desensitizedIdNumber(String idNumber){
if (!Strings.isNullOrEmpty(idNumber)) {
int length = idNumber.length();
if (length == 15){
return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1******$2");
}else if (length == 18){
return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1*********$2");
}else if(length > 9){
return StringUtils.left(idNumber,6).concat(generateAsterisk(idNumber.substring(6,length-3).length())).concat(StringUtils.right(idNumber,3));
}
}
return idNumber;
}
/**
* @description: 电子邮箱脱敏,脱敏规则:电子邮箱隐藏@前面的3个字符
* @return:
* @author: lx
* @time: 2022/6/22
*/
public static String desensitizationEmail(String email) {
if (StringUtils.isEmpty(email)) {
return email;
}
String encrypt = email.replaceAll("(\\w+)\\w{3}@(\\w+)", "$1***@$2");
if (email.equalsIgnoreCase(encrypt)) {
encrypt = email.replaceAll("(\\w*)\\w{1}@(\\w+)", "$1*@$2");
}
return encrypt;
}
/**
* @description: 地址脱敏,脱敏规则:从第4位开始隐藏,隐藏8位
* @return:
* @author: lx
* @time: 2022/6/22
*/
public static String desensitizedAddress(String address){
if (!Strings.isNullOrEmpty(address)) {
int length = address.length();
if(length > 4 && length <= 12){
return StringUtils.left(address, 3).concat(generateAsterisk(address.substring(3).length()));
}else if(length > 12){
return StringUtils.left(address,3).concat("********").concat(address.substring(11));
}else {
return address;
}
}
return address;
}
/**
* @description: 银行账号脱敏, 脱敏规则:银行账号保留前六后四
* @return:
* @author: lx
* @time: 2022/6/23
*/
public static String desensitizedAddressBankCardNum(String acctNo) {
if (StringUtils.isNotEmpty(acctNo)) {
String regex = "(\\w{6})(.*)(\\w{4})";
Matcher m = Pattern.compile(regex).matcher(acctNo);
if (m.find()) {
String rep = m.group(2);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < rep.length(); i++) {
sb.append("*");
}
acctNo = acctNo.replaceAll(rep, sb.toString());
}
}
return acctNo;
}
/**
* @description: 返回指定长度*字符串
* @return:
* @author: lx
* @time: 2022/6/22
*/
private static String generateAsterisk(int length){
String result = "";
for (int i = 0; i < length; i++) {
result += "*";
}
return result;
}
}
虽然有些美中不足,但足以适合我当前的项目环境,以后我还会进行修改的,谢谢大家的阅读。