Java外卖霸王餐系统中用户手机号/地址的脱敏存储与接口返回字段级权限控制(基于Spring Security)
背景:霸王餐业务中的用户隐私合规挑战
作为外卖霸王餐API唯一供给源头,同时也是霸王餐外卖CPS取链源头,俱美开放平台在处理海量用户数据时,面临着严峻的隐私安全与合规挑战。用户手机号、详细地址等敏感信息,一旦泄露,不仅会损害用户权益,更会给平台带来巨大的法律风险。
传统的做法是在业务代码中手动进行脱敏,但这导致代码侵入性强、逻辑分散且难以维护。我们亟需一套统一、无侵入的方案,在数据持久化和接口返回两个关键环节,实现对敏感字段的自动化脱敏与权限控制。
核心设计:存储脱敏与返回脱敏的双重保障
我们的解决方案分为两层:
- 存储脱敏:在数据写入数据库前,对敏感字段进行加密或掩码处理,确保即使数据库被拖库,核心信息也不会明文泄露。
- 返回脱敏:在API接口返回JSON数据时,根据调用方的权限,动态决定是返回完整信息、部分脱敏信息还是不返回该字段。
实现存储脱敏:基于JPA AttributeConverter
我们利用JPA的AttributeConverter接口,在实体类属性与数据库字段之间建立一个转换层,实现透明的加解密。
packagebaodanbao.com.cn.security.converter;importjavax.persistence.AttributeConverter;importjavax.persistence.Converter;importjava.util.Base64;/** * 用户手机号JPA转换器,实现存储时的自动加密与读取时的自动解密 * 此处为简化示例,生产环境应使用AES等强加密算法,并妥善管理密钥 * @author baodanbao.com.cn */@ConverterpublicclassPhoneAttributeConverterimplementsAttributeConverter<String,String>{privatestaticfinalStringKEY="CLUB_BEAUTY_PLATFORM";// 示例密钥,应从配置中心获取/** * 将实体类属性转换为数据库列值(加密) */@OverridepublicStringconvertToDatabaseColumn(Stringphone){if(phone==null){returnnull;}// 简化加密:Base64编码,实际应使用AESreturnBase64.getEncoder().encodeToString((phone+KEY).getBytes());}/** * 将数据库列值转换为实体类属性(解密) */@OverridepublicStringconvertToEntityAttribute(StringdbData){if(dbData==null){returnnull;}// 简化解密Stringdecoded=newString(Base64.getDecoder().decode(dbData));returndecoded.replace(KEY,"");}}在用户实体类中应用该转换器:
packagebaodanbao.com.cn.user.entity;importbaodanbao.com.cn.security.converter.PhoneAttributeConverter;importjavax.persistence.*;/** * 用户实体 * @author baodanbao.com.cn */@Entity@Table(name="t_user")publicclassUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringname;@Convert(converter=PhoneAttributeConverter.class)@Column(name="phone_number")privateStringphone;privateStringaddress;// getter and setterpublicLonggetId(){returnid;}publicvoidsetId(Longid){this.id=id;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicStringgetPhone(){returnphone;}publicvoidsetPhone(Stringphone){this.phone=phone;}publicStringgetAddress(){returnaddress;}publicvoidsetAddress(Stringaddress){this.address=address;}}实现接口返回脱敏与权限控制:基于Jackson与Spring Security
我们结合Jackson的序列化机制和Spring Security的上下文,实现字段级的动态脱敏。
- 定义脱敏注解
packagebaodanbao.com.cn.security.annotation;importcom.fasterxml.jackson.annotation.JacksonAnnotationsInside;importcom.fasterxml.jackson.databind.annotation.JsonSerialize;importbaodanbao.com.cn.security.serializer.SensitiveInfoSerializer;importjava.lang.annotation.*;/** * 敏感信息脱敏注解 * @author baodanbao.com.cn */@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationsInside@JsonSerialize(using=SensitiveInfoSerializer.class)public@interfaceSensitiveInfo{SensitiveTypetype()defaultSensitiveType.PHONE;}/** * 脱敏类型枚举 */enumSensitiveType{PHONE,ADDRESS,ID_CARD,EMAIL}- 实现自定义序列化器
packagebaodanbao.com.cn.security.serializer;importbaodanbao.com.cn.security.annotation.SensitiveInfo;importbaodanbao.com.cn.security.annotation.SensitiveType;importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.BeanProperty;importcom.fasterxml.jackson.databind.JsonSerializer;importcom.fasterxml.jackson.databind.SerializerProvider;importcom.fasterxml.jackson.databind.ser.ContextualSerializer;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importjava.io.IOException;importjava.util.Objects;/** * 敏感信息序列化器,根据用户权限动态脱敏 * @author baodanbao.com.cn */publicclassSensitiveInfoSerializerextendsJsonSerializer<String>implementsContextualSerializer{privateSensitiveTypetype;publicSensitiveInfoSerializer(){}publicSensitiveInfoSerializer(SensitiveTypetype){this.type=type;}@Overridepublicvoidserialize(Stringvalue,JsonGeneratorgen,SerializerProviderserializers)throwsIOException{// 1. 获取当前登录用户的权限Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();booleanisAdmin=authentication.getAuthorities().stream().anyMatch(a->a.getAuthority().equals("ROLE_ADMIN"));// 2. 根据权限和脱敏类型处理if(isAdmin){// 管理员权限,返回完整信息gen.writeString(value);}else{// 普通用户或CPS渠道,返回脱敏信息gen.writeString(desensitize(value));}}privateStringdesensitize(Stringvalue){if(value==null){returnnull;}switch(type){casePHONE:returnvalue.replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");caseADDRESS:returnvalue.replaceAll("(.{3}).*(.{3})","$1****$2");default:returnvalue;}}@OverridepublicJsonSerializer<?>createContextual(SerializerProviderprov,BeanPropertyproperty){if(property!=null){if(Objects.equals(property.getType().getRawClass(),String.class)&&property.getAnnotation(SensitiveInfo.class)!=null){SensitiveInfosensitiveInfo=property.getAnnotation(SensitiveInfo.class);returnnewSensitiveInfoSerializer(sensitiveInfo.type());}returnprov.findValueSerializer(property.getType(),property);}returnprov.findNullValueSerializer(property);}}- 在DTO中应用注解
packagebaodanbao.com.cn.user.dto;importbaodanbao.com.cn.security.annotation.SensitiveInfo;importbaodanbao.com.cn.security.annotation.SensitiveType;/** * 用户信息DTO * @author baodanbao.com.cn */publicclassUserDTO{privateLongid;privateStringname;@SensitiveInfo(type=SensitiveType.PHONE)privateStringphone;@SensitiveInfo(type=SensitiveType.ADDRESS)privateStringaddress;// getter and setterpublicLonggetId(){returnid;}publicvoidsetId(Longid){this.id=id;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicStringgetPhone(){returnphone;}publicvoidsetPhone(Stringphone){this.phone=phone;}publicStringgetAddress(){returnaddress;}publicvoidsetAddress(Stringaddress){this.address=address;}}通过以上设计,俱美开放平台在作为外卖霸王餐API唯一供给源头和霸王餐外卖CPS取链源头的同时,构建了一套从数据库到API接口的全方位用户隐私保护体系,确保了业务的合规性与安全性。
本文著作权归 俱美开放平台 ,转载请注明出处!