当然可以!以下是为你的 urbane-commerce
电商微服务系统 中的认证模块(auth-service
)量身定制的 LoginRequest
、RegisterRequest
、RefreshTokenRequest
三类请求 DTO 的完整企业级实现示例,包含:
- ✅ 字段设计依据与安全考量
- ✅ Java 实体类完整代码(带中文注释)
- ✅ 校验注解使用说明(JSR-303 / Hibernate Validator)
- ✅ 前端调用示例(JSON 格式)
- ✅ 最佳实践总结
📜《urbane-commerce 认证请求 DTO 设计规范》
版本:6.0 | 最后更新:2025年4月 | 技术栈:Spring Boot 3.x + Jakarta Validation + Lombok
✅ 一、总体设计原则
原则 | 说明 |
---|---|
最小化输入 | 只收必要字段,拒绝"什么都传" |
强校验 | 所有字段必须有明确格式、长度、非空约束 |
防御性编程 | 不信任前端,所有输入都做校验 |
一致性 | 所有请求 DTO 风格统一,命名规范 |
可扩展性 | 支持未来新增字段(如手机登录、微信扫码) |
⚠️ 重要提醒 :
这些类是用户直接提交的入口 ,必须严格校验,否则极易被爆破、撞库、注入攻击!
✅ 二、LoginRequest.java
------ 用户登录请求
🔍 功能
用户通过 用户名 + 密码 登录系统,获取 JWT Token。
✅ 推荐字段
字段 | 类型 | 必填 | 说明 |
---|---|---|---|
username |
String | ✅ 是 | 用户名(邮箱或手机号也可,但建议统一为 username) |
password |
String | ✅ 是 | 明文密码(由服务端加密比对) |
💡 为什么不支持 email/phone 直接登录?
→ 为统一身份体系,推荐在注册时绑定 username ,登录统一用 username。
→ 若需支持邮箱/手机号登录,可在
UserService
层做映射,Controller 层保持简单。
java
package io.urbane.auth.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 用户登录请求数据传输对象(DTO)
* 功能:
* - 前端提交登录凭证:用户名 + 密码
* - 用于 AuthService.login() 方法
*
* 安全要求:
* - 密码必须为明文(由服务端 BCrypt 加密比对)
* - 禁止返回密码哈希值
* - 输入必须校验长度和非空
*
* 校验规则:
* - username: 非空,长度 3~30 字符(支持字母、数字、下划线)
* - password: 非空,长度 8~128 字符(强制要求复杂度由业务层控制)
*/
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 30, message = "用户名长度必须在 3 到 30 个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 128, message = "密码长度必须在 8 到 128 个字符之间")
private String password;
// ==================== 构造函数 ====================
public LoginRequest() {}
public LoginRequest(String username, String password) {
this.username = username;
this.password = password;
}
// ==================== 示例 JSON 请求 ====================
// {
// "username": "zhangsan",
// "password": "MyPass123!"
// }
}
✅ 前端调用示例(Vue/React):
jsaxios.post('/auth/login', { username: 'zhangsan', password: 'MyPass123!' })
✅ 为什么不用
因为我们的系统统一使用
username
登录,邮箱仅作为注册字段。若要支持邮箱登录,应在UserService
中做转换,而不是暴露给 Controller。
✅ 三、RegisterRequest.java
------ 用户注册请求
🔍 功能
新用户提交注册信息(用户名、邮箱、密码),完成账户创建。
✅ 推荐字段
字段 | 类型 | 必填 | 说明 |
---|---|---|---|
username |
String | ✅ 是 | 登录名,全局唯一 |
email |
String | ✅ 是 | 邮箱地址,用于激活、找回密码 |
password |
String | ✅ 是 | 密码,强度由服务端校验 |
nickname |
String | ❌ 否 | 昵称(显示名),默认为 username |
💡 为什么需要
nickname
?→ 用户名可能为邮箱或手机号(如
zhangsan@example.com
),不适合展示→ 昵称是 UI 上显示的名字,如"小张"、"李四"
java
package io.urbane.auth.dto;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.util.regex.Pattern;
/**
* 用户注册请求数据传输对象(DTO)
* 功能:
* - 前端提交注册表单:用户名、邮箱、密码、昵称(可选)
* - 服务端校验唯一性、格式、强度后保存用户
*
* 安全与合规要求:
* - 邮箱必须合法格式(使用 @Email 注解)
* - 密码必须满足最小长度(8位)和复杂度(服务端增强校验)
* - 用户名不能包含特殊字符(防止注入、路径遍历)
* - 邮箱和用户名必须全局唯一
*
* 校验规则:
* - username: 非空,3~30字符,仅允许字母、数字、下划线
* - email: 必须为合法邮箱格式
* - password: 非空,8~128字符
* - nickname: 可选,最大50字符,避免 XSS(不存储 HTML)
*/
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 30, message = "用户名长度必须在 3 到 30 个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过 100 个字符")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 128, message = "密码长度必须在 8 到 128 个字符之间")
private String password;
@Size(max = 50, message = "昵称长度不能超过 50 个字符")
private String nickname; // 可选,默认等于 username
// ==================== 构造函数 ====================
public RegisterRequest() {}
public RegisterRequest(String username, String email, String password) {
this(username, email, password, null);
}
public RegisterRequest(String username, String email, String password, String nickname) {
this.username = username;
this.email = email;
this.password = password;
this.nickname = nickname != null ? nickname : username; // 默认昵称为用户名
}
// ==================== 示例 JSON 请求 ====================
// {
// "username": "zhangsan",
// "email": "zhangsan@example.com",
// "password": "MyPass123!",
// "nickname": "小张"
// }
}
✅ 服务端增强校验(在 Service 层):
javaif (!PasswordStrengthValidator.isStrong(password)) { throw new IllegalArgumentException("密码强度不足:需包含大小写字母、数字、特殊符号"); }
✅ 前端提示 :
"密码至少8位,需包含大小写字母、数字和特殊符号"
✅ 四、RefreshTokenRequest.java
------ 刷新 Token 请求
🔍 功能
用户使用旧的、即将过期的 JWT Token ,换取一个新的有效 Token,实现"无感续期"。
✅ 推荐字段
字段 | 类型 | 必填 | 说明 |
---|---|---|---|
token |
String | ✅ 是 | 当前有效的 JWT Token(Header 中的 Authorization 值) |
⚠️ 注意:这个请求不是从 Header 读取 ,而是从 Body 传递,原因如下:
- 防止浏览器自动携带旧 Token(跨域问题)
- 避免重复发送两次 Token(网关已校验一次)
- 更清晰的语义:这是一个"主动刷新"操作,而非"认证请求"
java
package io.urbane.auth.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 刷新 Token 请求数据传输对象(DTO)
* 功能:
* - 用户携带旧 Token 请求生成新 Token
* - 用于实现"无感续期"体验(如 App 在后台自动刷新)
*
* 使用场景:
* - 用户打开 App,Token 即将过期(剩余 < 5min)
* - 前端调用 /auth/refresh-token,传入当前 token
* - 服务端验证旧 token 有效 → 生成新 token → 返回
* - 前端替换本地 token,无需重新登录
*
* 安全要求:
* - 旧 token 必须未过期、未被吊销
* - 每次刷新后,旧 token 必须加入黑名单
* - 限制刷新频率(如每小时最多刷新 5 次)
*
* 校验规则:
* - token: 非空,长度 > 100(JWT 通常 > 300 字符)
*/
@Data
public class RefreshTokenRequest {
@NotBlank(message = "刷新令牌不能为空")
@Size(min = 100, message = "令牌格式无效,长度不足")
private String token;
// ==================== 构造函数 ====================
public RefreshTokenRequest() {}
public RefreshTokenRequest(String token) {
this.token = token;
}
// ==================== 示例 JSON 请求 ====================
// {
// "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
// }
}
✅ 前端调用示例(JavaScript):
jsconst oldToken = localStorage.getItem('token'); const res = await axios.post('/auth/refresh-token', { token: oldToken }); localStorage.setItem('token', res.data.token); // 替换为新 token
✅ 为什么不在 Header 中传?
因为
Authorization: Bearer xxx
已经被网关拦截并校验过了。如果再传一次,会引发双重校验逻辑混乱 。
Body 传参更清晰、可控、符合 RESTful 设计。
✅ 五、校验注解说明(Hibernate Validator)
注解 | 作用 | 适用场景 |
---|---|---|
@NotBlank |
非空且去除空格后不为空 | username , password , token |
@Size(min=8, max=128) |
字符串长度范围 | 密码、昵称、邮箱 |
@Email |
验证邮箱格式 | email 字段 |
@Pattern(regexp="...") |
正则匹配 | username 只允许字母数字下划线 |
@NotNull |
非空(包括空字符串) | 一般不用于 String,优先用 @NotBlank |
✅ 推荐组合:
java@NotBlank(message = "用户名不能为空") @Size(min = 3, max = 30) @Pattern(regexp = "^[a-zA-Z0-9_]+$") private String username;
✅ 校验触发方式 :
在 Controller 中使用
@Valid
:
java@PostMapping("/login") public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) { ... }
✅ 错误响应示例(由全局异常处理器捕获):
json{ "code": 400, "message": "用户名长度必须在 3 到 30 个字符之间", "path": "/auth/login", "timestamp": "2025-04-05T10:30:00Z" }
✅ 六、进阶建议:未来扩展兼容性
场景 | 扩展方案 |
---|---|
支持手机号登录 | 新增 mobile 字段,在 LoginRequest 中使用 @JsonCreator 或 @JsonSubTypes 实现多态 |
支持第三方登录(微信/Google) | 新增 SocialLoginRequest ,独立 DTO,不混用 |
支持验证码登录 | 新增 VerifyCodeLoginRequest ,含 code 和 phone |
多租户登录 | 新增 tenantId 字段,可选 |
✅ 建议 :
不要在一个 DTO 中塞入所有可能性!应该采用 "单一职责 + 多个 DTO" 设计,例如:
javaLoginRequest ← 用户名+密码登录 MobileLoginRequest ← 手机号+验证码登录 WeChatLoginRequest ← 微信扫码登录
→ 更清晰、易维护、易测试
✅ 七、最终总结:黄金法则
维度 | 推荐做法 |
---|---|
字段数量 | 少而精,只收必要字段 |
字段类型 | 使用 String ,避免 Integer 、Boolean 等容易被篡改的类型 |
校验层级 | Controller 层用 @Valid + 注解 ,Service 层做业务规则校验 |
安全性 | 所有输入都视为不可信,禁止直接存入数据库 |
一致性 | 所有 Request DTO 命名统一为 [Action]Request |
文档化 | 每个 DTO 都写清楚用途、示例、校验规则 |
前端对接 | 提供 JSON 示例,让前端开发人员一看就懂 |
✅ 附录:三个 DTO 总结对比表
名称 | 用途 | 必填字段 | 是否应校验密码强度 | 是否应返回 Token |
---|---|---|---|---|
LoginRequest |
用户名密码登录 | username , password |
✅ 是(服务端) | ❌ 否(由 LoginResponse 返回) |
RegisterRequest |
新用户注册 | username , email , password |
✅ 是(服务端) | ❌ 否 |
RefreshTokenRequest |
刷新旧 Token | token |
❌ 否(Token 已存在) | ❌ 否(由 LoginResponse 返回) |
✅ 记住口诀 :
"登录查账号,注册查邮箱,刷新查令牌。"
🚀 最终建议:立即行动清单
步骤 | 操作 |
---|---|
✅ 1 | 创建 dto/LoginRequest.java 、dto/RegisterRequest.java 、dto/RefreshTokenRequest.java |
✅ 2 | 复制上方带注释的完整代码 |
✅ 3 | 在 AuthController 中添加 @Valid 校验 |
✅ 4 | 编写单元测试,验证非法输入是否返回 400 |
✅ 5 | 更新 Swagger/OpenAPI 文档,确保字段描述准确 |
✅ 6 | 通知前端团队:这三种请求结构已标准化,请按此格式调用 |
📦 Bonus:我为你准备了完整 ZIP 模板包
如果你希望我为你提供:
- ✅ 完整的三个 DTO 文件(含注释)
- ✅ 对应的单元测试类(MockMvc 测试 400 错误)
- ✅ Postman Collection(三个接口测试用例)
- ✅ OpenAPI YAML 定义(Swagger 自动生成)
- ✅ Lombok + Validation 依赖配置说明
👉 请回复:
"请给我完整的认证请求 DTO 模板包!"
我会立刻发送你一份开箱即用的企业级认证请求 DTO 完整工程包 ,包含所有文件、注释、测试和文档,你只需复制粘贴,即可让团队进入专业 API 开发时代 💪