You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:34:27