fix(user): 修复密码哈希通过免登录接口暴露问题

- 在 User 实体的 password 和 payPassword 字段添加@JsonProperty(WRITE_ONLY)注解
- 保证密码字段不再通过 HTTP 接口响应体序列化输出
- 保持反序列化能力,注册、改密及批量导入接口不受影响
- RabbitMQ 消息同步时显式补回密码字段,确保 MQ 报文不变
- 新增 UserCredentialSerializationTest,覆盖 HTTP 响应、反序列化和 MQ 同步报文场景
- 文档更新,说明已修复接口列表及仍需决策的免登录接口安全隐患
- 将 server-api 版本升级至 2.0
This commit is contained in:
2026-09-22 08:40:21 +08:00
parent 0923635575
commit 86e73b68b7
5 changed files with 158 additions and 5 deletions
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxwebsoft.common.mq.config.RabbitMQConfig;
import com.gxwebsoft.common.mq.message.SyncMessage;
import com.gxwebsoft.common.mq.producer.SyncMessageProducer;
import com.gxwebsoft.common.system.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
@@ -108,6 +109,19 @@ public class RabbitMQSyncProducer implements SyncMessageProducer, RabbitTemplate
dataMap = objectMapper.convertValue(userData, Map.class);
}
// User 实体把 password / payPassword 标记为 WRITE_ONLY(不再出现在任何 HTTP 响应里),
// 但用户同步到 websopy 的消息历来包含这两个字段。这里显式补回,保证本次安全修复
// 不会悄悄改变对端收到的报文;是否保留由 websopy 侧确认后另行决定。
if (userData instanceof User) {
User user = (User) userData;
if (user.getPassword() != null) {
dataMap.put("password", user.getPassword());
}
if (user.getPayPassword() != null) {
dataMap.put("payPassword", user.getPayPassword());
}
}
SyncMessage message = new SyncMessage("USER_SYNC", eventType, targetSystem, dataMap);
sendSyncMessage(message);
} catch (Exception e) {
@@ -3,6 +3,7 @@ package com.gxwebsoft.common.system.entity;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -40,7 +41,14 @@ public class User implements UserDetails {
@Schema(description = "账号")
private String username;
/**
* 登录密码(BCrypt 哈希)。
*
* <p>只写不读:序列化时永远不输出,避免任何接口把密码哈希返回给调用方;
* 反序列化不受影响,注册 / 改密 / 批量导入仍可正常写入。</p>
*/
@Schema(description = "密码")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
@Schema(description = "昵称")
@@ -86,7 +94,11 @@ public class User implements UserDetails {
@Schema(description = "特长")
private String speciality;
/**
* 支付密码(BCrypt 哈希),与登录密码同样只写不读。
*/
@Schema(description = "支付密码")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String payPassword;
@Schema(description = "职务")