想看真实数据,那得加钱
即使相逢真的不再可能,爱情仍然是爱情
要在springboot项目中实现数据脱敏,保护用户数据安全, 有什么方案吗
数据脱敏
数据脱敏(Data Masking)是一种数据保护技术,用于保护敏感数据的隐私安全。数据脱敏的目的是将敏感数据中的某些字段或部分信息进行隐藏或替换,以避免这些数据被未经授权的人员或者恶意攻击者窃取、篡改、泄漏或滥用。
在数据脱敏的过程中,脱敏算法需要尽可能地保持原始数据的格式和数据结构,同时对敏感信息进行掩盖或者替换,以达到保护隐私的目的。通常,数据脱敏技术可以采用以下方式:
-
隐藏:通过删除或者替换数据中的某些字段或者部分信息,来达到隐藏敏感数据的目的。例如,在电子邮件地址中隐藏@符号,或者隐藏信用卡号码的后四位。
-
脱敏:通过对数据进行特定算法的加密、模糊化或者扰乱,来达到脱敏敏感数据的目的。例如,对于手机号码进行脱敏,可以将中间四位用 * 替换。
-
伪装:通过随机生成一些数据,并与真实数据混合在一起,来达到保护隐私的目的。例如,在数据中添加一些无意义的字符或数
数据脱敏的方案
-
重载 get 方法, 通过参数获取脱敏还是原始数据
-
WebMvcConfigurerAdapter/HandlerInterceptorAdapter 拦截反射getset脱敏字段
-
在数据库层面进行脱敏
在数据库层面进行脱敏是一种比较彻底的方案。可以使用数据库自带的加密算法,也可以使用第三方的加密库,例如 PGP 等。
- 使用注解进行脱敏
在实体类中可以添加一些注解,例如 @SensitiveData,用来标记需要进行脱敏的字段。然后在服务层或者控制层中使用 AOP 技术来实现数据脱敏。具体实现可以参考 Spring AOP 的文档。
需要注意的是,无论选择哪种方案,都需要考虑到数据的保密性和可读性,确保数据在传输和存储过程中都得到了保护。同时也需要考虑数据脱敏后对业务的影响,避免脱敏后无法满足业务需求。
WebMvcConfigurerAdapter 来实现数据脱敏
- 编写脱敏逻辑
编写一个工具类,例如 SensitiveDataUtils,用来实现具体的脱敏逻辑。可以根据业务需求来确定脱敏方式和长度。例如,对于手机号码进行脱敏,可以使用正则表达式来匹配手机号码,然后将中间四位用 * 替换。
public class SensitiveDataUtils {
public static String desensitize(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
String result = value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
return result;
}
}
- 实现 WebMvcConfigurerAdapter
实现一个继承自 WebMvcConfigurerAdapter 的拦截器类 SensitiveDataInterceptor,重写其中的 postHandle() 方法。在 postHandle() 方法中,通过反射获取到需要脱敏的字段,然后调用 SensitiveDataUtils 中的方法进行脱敏处理。
public class SensitiveDataInterceptor extends WebMvcConfigurerAdapter {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView == null || modelAndView.getModel() == null) {
return;
}
for (String key : modelAndView.getModel().keySet()) {
Object value = modelAndView.getModel().get(key);
if (value == null) {
continue;
}
Class<?> clazz = value.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(SensitiveData.class) == null) {
continue;
}
String fieldName = field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
Method getMethod = clazz.getMethod(getMethodName);
Object fieldValue = getMethod.invoke(value);
if (fieldValue != null && fieldValue instanceof String) {
Method setMethod = clazz.getMethod(setMethodName, String.class);
setMethod.invoke(value, SensitiveDataUtils.desensitize((String) fieldValue));
}
} catch (Exception e) {
continue;
}
}
}
}
}
- 配置拦截器
在 Spring Boot 的配置类中,将拦截器添加到拦截器链中。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SensitiveDataInterceptor());
}
}
- 在实体类中使用注解
在实体类中,可以对需要进行脱敏的字段加上 @SensitiveData 注解。例如:
public class User {
@SensitiveData
private String mobile;
// 省略其他属性和方法
}
在控制层或者服务层中,如果需要返回 User 对象,拦截器会自动拦截并进行脱敏处理。例如:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
在上述示例中,如果查询到的 User 对象的 mobile 字段为 “13812345678”,在返回给客户端之前,拦截器会自动将其脱敏为 “138****5678”。
使用 WebMvcConfigurerAdapter 进行数据脱敏的方案,可以灵活定制脱敏逻辑,不需要在实体类中添加额外的代码,同时也不需要使用 AOP 或者自定义序列化器,可以直接拦截控制器返回的对象进行处理。需要注意的是,拦截器可能会对性能产生一定的影响,应该谨慎使用。
WebMvcConfigurerAdapter | 适配器 Spring 5.0+ 过时
WebMvcConfigurerAdapter 是 Spring MVC 提供的一个适配器类,用于在 Spring MVC 的配置中进行个性化定制。WebMvcConfigurerAdapter 主要的作用是提供了一种简便的方式,让开发者通过重写该类中的方法,来对 Spring MVC 进行定制化配置,比如配置消息转换器、拦截器等。在 Spring 5.0 版本之后,WebMvcConfigurerAdapter 被标注为了过时状态。
WebMvcConfigurerAdapter 主要是为了解决 Spring MVC 中需要配置大量的 Bean 的问题,通过提供一些默认的实现,减少了开发者的配置量,同时也保证了 Spring MVC 的灵活性。通过实现 WebMvcConfigurerAdapter 接口,开发者可以自定义拦截器、视图解析器、异常处理器等等,以满足个性化需求。同时,WebMvcConfigurerAdapter 可以继承并扩展 WebMvcConfigurer 接口中的方法,提供更加方便的配置方式。
WebMvcConfigurerAdapter 的主要功能如下:
-
配置消息转换器,用于处理请求和响应的消息体格式。
-
配置静态资源映射,用于将请求映射到静态资源。
-
配置拦截器,用于在请求处理前或处理后添加逻辑。
-
配置跨域资源共享(CORS)策略,用于支持跨域请求。
-
配置异常处理器,用于处理全局异常。
WebMvcConfigurerAdapter 的优势在于,可以通过重写一些方法来对 Spring MVC 进行个性化定制化配置,提高了开发者的效率。同时,通过继承并扩展 WebMvcConfigurer 接口中的方法,WebMvcConfigurerAdapter 提供了更加方便的配置方式。
WebMvcConfigurerAdapter 的劣势在于,需要开发者手动编写大量的代码来完成个性化的定制化配置,而且需要开发者对 Spring MVC 的内部机制比较熟悉,否则容易出现配置错误导致程序出错。另外,WebMvcConfigurerAdapter 本身也存在一定的性能开销,可能会对系统的性能产生一定的影响。
不使用 过时的WebMvcConfigurerAdapter , 自定义拦截器,同样实现数据脱敏是吧?
实现拦截器
实现一个继承自 HandlerInterceptorAdapter 的拦截器类 SensitiveDataInterceptor,重写其中的 postHandle() 方法。在 postHandle() 方法中,通过反射获取到需要脱敏的字段,然后调用 SensitiveDataUtils 中的方法进行脱敏处理。
public class SensitiveDataInterceptor extends HandlerInterceptorAdapter {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView == null || modelAndView.getModel() == null) {
return;
}
for (String key : modelAndView.getModel().keySet()) {
Object value = modelAndView.getModel().get(key);
if (value == null) {
continue;
}
Class<?> clazz = value.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(SensitiveData.class) == null) {
continue;
}
String fieldName = field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
Method getMethod = clazz.getMethod(getMethodName);
Object fieldValue = getMethod.invoke(value);
if (fieldValue != null && fieldValue instanceof String) {
Method setMethod = clazz.getMethod(setMethodName, String.class);
setMethod.invoke(value, SensitiveDataUtils.desensitize((String) fieldValue));
}
} catch (Exception e) {
continue;
}
}
}
}
}
注册拦截器
在 Spring Boot 的配置类中,将拦截器注册到拦截器链中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SensitiveDataInterceptor());
}
}
使用注解进行脱敏
在实体类中添加一个注解,例如 @SensitiveData,来标记需要进行脱敏的字段。然后在服务层或者控制层中使用 AOP 技术来实现数据脱敏。具体步骤如下:
- 定义注解
在实体类中定义一个注解,例如 @SensitiveData,用来标记需要进行脱敏的字段。可以根据业务需求来定义注解的属性,例如脱敏类型、前缀长度、后缀长度等。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
SensitiveType type() default SensitiveType.DEFAULT;
int prefix() default 0;
int suffix() default 0;
}
- 编写脱敏逻辑
编写一个工具类,例如 SensitiveDataUtils,用来实现具体的脱敏逻辑。可以根据注解中的属性来确定脱敏方式和长度。例如,对于手机号码进行脱敏,可以使用正则表达式来匹配手机号码,然后将中间四位用 * 替换。
public class SensitiveDataUtils {
public static String desensitize(String value, SensitiveData sensitiveData) {
if (StringUtils.isBlank(value)) {
return value;
}
String result = value;
switch (sensitiveData.type()) {
case MOBILE:
result = desensitizeMobile(value);
break;
case DEFAULT:
default:
break;
}
if (sensitiveData.prefix() > 0) {
result = StringUtils.overlay(result, StringUtils.repeat("*", sensitiveData.prefix()), 0, sensitiveData.prefix());
}
if (sensitiveData.suffix() > 0) {
result = StringUtils.overlay(result, StringUtils.repeat("*", sensitiveData.suffix()), result.length() - sensitiveData.suffix(), result.length());
}
return result;
}
public static String desensitizeMobile(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
}
- 定义切面
定义一个切面,例如 SensitiveDataAspect,用来拦截被 @SensitiveData 标记的字段,并进行脱敏处理。在切面中使用反射来获取注解的属性,并调用 SensitiveDataUtils 中的方法进行脱敏处理。
@Aspect
@Component
public class SensitiveDataAspect {
@Around("@annotation(sensitiveData)")
public Object desensitize(ProceedingJoinPoint pjp, SensitiveData sensitiveData) throws Throwable {
Object result = pjp.proceed();
if (result != null && result instanceof String) {
return SensitiveDataUtils.desensitize((String) result, sensitiveData);
}
return result;
}
}
- 配置切面
在 Spring Boot 的配置类中,将切面加入到 AOP 的切面列表中。
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public SensitiveDataAspect sensitiveDataAspect() {
return new SensitiveDataAspect();
}}
- 在实体类中使用注解
在实体类中,可以对需要进行脱敏的字段加上 @SensitiveData 注解。例如:
public class User {
@SensitiveData(type = SensitiveType.MOBILE, prefix = 3, suffix = 4)
private String mobile;
// 省略其他属性和方法
}
在控制层或者服务层中,如果需要返回 User 对象,切面会自动拦截并进行脱敏处理。例如:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
在上述示例中,如果查询到的 User 对象的 mobile 字段为 “13812345678”,在返回给客户端之前,切面会自动将其脱敏为 “138****5678”。
使用注解进行脱敏的方案,可以根据业务需求自由定制脱敏逻辑,同时也能保证脱敏的粒度较细,不会影响到其他字段。需要注意的是,在使用 AOP 进行数据脱敏时,会增加一定的性能开销,应该谨慎使用。
第四种方案, 我还有一种办法, 我现在自定义JacksonJson的JSON序列化 ,Serializer写自定义注解
通过自定义 Jackson 的 JSON 序列化器来实现数据脱敏。下面是具体的实现步骤:
- 定义注解
在需要脱敏的字段上添加自定义注解,例如 @SensitiveData。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
SensitiveType type() default SensitiveType.DEFAULT;
int prefix() default 0;
int suffix() default 0;
}
- 编写脱敏逻辑
编写一个工具类,例如 SensitiveDataUtils,用来实现具体的脱敏逻辑。可以根据注解中的属性来确定脱敏方式和长度。例如,对于手机号码进行脱敏,可以使用正则表达式来匹配手机号码,然后将中间四位用 * 替换。
public class SensitiveDataUtils {
public static String desensitize(String value, SensitiveData sensitiveData) {
if (StringUtils.isBlank(value)) {
return value;
}
String result = value;
switch (sensitiveData.type()) {
case MOBILE:
result = desensitizeMobile(value);
break;
case DEFAULT:
default:
break;
}
if (sensitiveData.prefix() > 0) {
result = StringUtils.overlay(result, StringUtils.repeat("*", sensitiveData.prefix()), 0, sensitiveData.prefix());
}
if (sensitiveData.suffix() > 0) {
result = StringUtils.overlay(result, StringUtils.repeat("*", sensitiveData.suffix()), result.length() - sensitiveData.suffix(), result.length());
}
return result;
}
public static String desensitizeMobile(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
}
- 编写自定义序列化器
自定义一个序列化器 SensitiveDataSerializer,继承 JsonSerializer 类,并重写其中的 serialize() 方法。在序列化时,判断字段是否有 @SensitiveData 注解,如果有,则调用 SensitiveDataUtils 中的方法进行脱敏处理。
public class SensitiveDataSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
SensitiveData sensitiveData = serializers.getAttribute("sensitiveData");
String desensitizedValue = SensitiveDataUtils.desensitize(value, sensitiveData);
gen.writeString(desensitizedValue);
}
}
- 注册序列化器
在 Spring Boot 的配置类中,注册序列化器。可以使用 Jackson2ObjectMapperBuilder 来注册,也可以直接在 ObjectMapper 中注册。
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
SimpleModule sensitiveDataModule = new SimpleModule();
sensitiveDataModule.addSerializer(String.class, new SensitiveDataSerializer());
builder.modules(sensitiveDataModule);
};
}
}
- 在实体类中使用注解
在实体类中,可以对需要进行脱敏的字段加上 @SensitiveData 注解。例如:
public class User {
@SensitiveData(type = SensitiveType.MOBILE, prefix = 3, suffix = 4)
private String mobile;
// 省略其他属性和方法
}
- 序列化对象
在控制层或者服务层中,将需要返回给客户端的对象进行序列化。在序列化时,通过调用 ObjectMapper 的 writeValueAsString() 方法,自动触发 SensitiveDataSerializer 的序列化方法,并进行数据脱敏。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) throws JsonProcessingException {
User user = userService.getUserById(id);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
return json;
}
}
在上述示例中,如果查询到的 User 对象的 mobile 字段为 “13812345678”,在序列化为 JSON 字符串返回给客户端之前,序列化器会自动将其脱敏为 “138****5678”。
使用自定义序列化器进行数据脱敏的方案,可以灵活定制脱敏逻辑,不需要在实体类中添加额外的代码,可以将脱敏逻辑与数据的序列化分离,降低了代码的耦合度。但是,需要注意的是,自定义序列化器会增加一定的性能开销,应该谨慎使用。