Spring Boot中如何仅在日志/数据库中屏蔽敏感密码数据?
解决方案:分场景处理密码脱敏,不影响业务校验
你的核心问题是不能在DTO反序列化阶段直接替换密码,否则会丢失原始密码导致校验失败。下面分「日志脱敏」和「数据库存储脱敏」两个场景给出具体实现:
一、日志屏蔽密码(保留原始密码用于业务校验)
方法1:自定义序列化器(仅在序列化时脱敏)
给pwd字段添加序列化器而非反序列化器,这样DTO在被打印到日志/返回响应时自动屏蔽密码,但反序列化时仍保留用户传入的原始值:
// 自定义密码序列化器 public class MaskingSensitiveDataSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 非空则替换为***,空值直接输出 gen.writeString(value != null ? "***" : null); } }
修改SignInDTO:
public class SignInDTO { private String id; // 仅在序列化(日志打印、响应输出)时脱敏,反序列化保留原始密码 @JsonSerialize(using = MaskingSensitiveDataSerializer.class) private String pwd; // getter、setter 省略 }
方法2:AOP拦截请求日志(手动脱敏请求体)
如果不想修改DTO,可通过切面拦截所有请求,在打印日志前手动替换请求体中的密码字段:
@Aspect @Component public class RequestLogAspect { private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class); @Around("execution(* com.yourpackage.controller.*.*(..))") public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String requestBody = null; if (HttpMethod.POST.name().equals(request.getMethod())) { // 从缓存的请求体中读取(需提前通过Filter包装请求) ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request; requestBody = new String(wrappedRequest.getContentAsByteArray(), StandardCharsets.UTF_8); // 正则替换pwd字段值为*** requestBody = requestBody.replaceAll("\"pwd\":\"[^\"]*\"", "\"pwd\":\"***\""); } logger.info("请求URL:{},请求体:{}", request.getRequestURL(), requestBody); return joinPoint.proceed(); } }
配套请求缓存Filter(解决输入流只能读一次的问题):
@Component public class RequestCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); filterChain.doFilter(wrappedRequest, response); } }
二、数据库存储时屏蔽密码
在业务校验完成后,再对存入数据库的实体密码进行替换,避免影响校验逻辑:
方法1:服务层手动替换
@Service public class AuthService { private final UserRepository userRepository; public AuthDTO getInfo(SignInDTO signInDTO) { // 1. 先执行密码校验逻辑(比如和数据库原始密码/加密密码比对) boolean isValid = checkPassword(signInDTO.getId(), signInDTO.getPwd()); if (!isValid) { throw new RuntimeException("密码错误"); } // 2. 准备存入数据库的实体,替换密码为*** User user = new User(); user.setId(signInDTO.getId()); user.setPwd("***"); userRepository.save(user); // 3. 构造并返回AuthDTO AuthDTO authDTO = new AuthDTO(); // 填充字段逻辑 return authDTO; } // 密码校验逻辑示例 private boolean checkPassword(String id, String inputPwd) { // 从数据库查询用户原始密码(或加密后的密码)进行比对 User dbUser = userRepository.findById(id).orElseThrow(); return inputPwd.equals(dbUser.getPwd()); // 实际项目建议用加密比对,比如BCrypt } }
方法2:JPA实体监听自动替换
如果希望统一处理所有实体保存操作,可添加实体监听器:
@Entity @EntityListeners(UserEntityListener.class) public class User { @Id private String id; private String pwd; // getter、setter 省略 } // 实体监听类 public class UserEntityListener { // 保存/更新前自动替换密码 @PrePersist @PreUpdate public void onPreSave(User user) { if (user.getPwd() != null) { user.setPwd("***"); } } }
内容的提问来源于stack exchange,提问作者lolo
相关产品推荐
相关产品推荐

