hhjava 注册与登录¶
本文面向先学会调用接口的读者。第一次只做“注册 → App 密码登录 → 携带 Access Token”,成功后再学刷新和浏览器授权码流程,不需要一开始把所有协议都接入。
前置知识:密码、会话、令牌¶
| 名称 | 通俗解释 |
|---|---|
| 第一方 App | 由你自己控制、为自己的用户和后端服务开发的客户端;不是任意第三方都能套用的密码登录方式 |
| HTTP / HTTPS | HTTP 是请求响应协议,HTTPS 使用 TLS(Transport Layer Security,传输层安全)保护传输;本机调试地址不等于正式公网可使用明文 HTTP |
| JSON / Body / Header | JSON 是文本数据格式;Body 是请求体;Header 是请求头,例如内容类型和 Authorization |
| Session(会话) | 服务端记录一段持续的登录状态;浏览器常用 Cookie 中的会话 ID 找回它 |
| Cookie | 浏览器按域、路径等规则保存并自动附带的小段数据;本项目原生移动登录不依赖浏览器 Cookie |
| Access Token(访问令牌) | 短期调用业务接口的凭据;Bearer 表示持有者可以使用,泄露后别人也可能调用接口 |
| Refresh Token(刷新令牌) | 用于申请新的访问令牌,避免每次短期令牌到期都重新输密码;不拿它直接请求业务接口 |
| JWT(JSON Web Token) | 携带身份等声明的令牌格式;本项目 Access Token 经 RSA 私钥签名,资源服务通过 JWKS 公钥集合验证 |
| scope(授权范围) / 角色 / 所有权 | 分别表达获准访问的能力范围、业务身份、具体对象属于谁,不能只做登录检查就默认全有权限 |
| OAuth2 / OIDC / PKCE | 分别是授权框架、其上的身份层、授权码兑换证明机制;原生 App 主线无需手工操作浏览器这套参数,解释见 认证和授权 |
为什么数据库不保存原始密码¶
哈希是把输入按算法转换成摘要,不是拿一把密钥以后再解密还原。密码存储还需要随机盐和合适的计算成本,降低数据库泄露后的批量猜测效率。
BCrypt 将算法参数、随机盐及计算结果编码在保存的哈希字符串里。注册调用 encode 保存结果,登录调用 matches(输入密码, 数据库哈希) 验证;每次编码使用随机盐,同一密码两次 encode 不应靠字符串相等判断。当前项目不需要额外手工创建 salt 字段来拼密码。
密码哈希仍是敏感数据,不能返回给前端或输出日志。MD5/SHA-256 这种快速通用摘要不适合单独作为密码存储方案;HTTPS、密码哈希、JWT 签名分别保护传输、数据库密码和令牌完整性,不能互相替代。Spring Security 密码存储说明
怎样避免“每次请求都登录”¶
HTTP 请求之间本来不自动保留 Java 方法的变量。登录成功后客户端保存会话凭据,后续每次请求携带它,后端才有依据恢复可信身份。App 通常将凭据放在系统安全存储(如 iOS Keychain、Android Keystore 支撑的安全存储方案),不长期保存原始密码;重启 App 后可按策略用 Refresh Token 续期。
第一次:用户名 + 密码 → 验证 → Access Token + Refresh Token
平时: Access Token → 业务接口验签与授权 → 业务结果
到期: 最新 Refresh Token → 轮换 → 新的一对令牌
退出: Refresh Token → 撤销会话家族,客户端清理本地凭据
1. 先区分注册、登录、调用业务接口¶
- 注册:创建用户账号,保存密码哈希并建立默认角色关联。
- 登录:证明账号属于自己,取得后续请求使用的凭据。
- 调用业务接口:携带 Access Token,服务端验签并判断权限。
当前项目采用混合入口:第一方 App 原生用户名密码登录,纯浏览器 Web/H5/SPA 使用 OAuth2 Authorization Code + PKCE。两种方式由同一个 user 服务认证用户并统一签发 RSA JWT,不是两个独立账号系统。
本文负责接口使用;底层 Provider、principal 和角色查询见 认证和授权,协议概念见 OAuth2/OIDC 学习笔记。
开始调试前准备什么¶
- Mac 上安装 Reqable;IDEA 导入 hhjava 根 Maven 工程,使用项目 JDK 17。先确认 user 与 gateway 的启动日志没有失败,而不是只看运行按钮变绿。
- 按 user 的实际配置准备 MySQL、Nacos 与开发秘密文件,确保数据库迁移和用户角色基础数据就绪。这里不重新创建数据库,也不根据文档猜远端服务器状态。
- 在 gateway 当前生效配置/启动日志确认实际端口,结合部署域名确定入口地址;在 Nacos 检查
/user路由目标与去前缀规则。 - 选择专门的学习账号,不使用真实用户数据。数据库账号密码、Nacos 管理员密码、MinIO 访问密钥都不是这里注册的用户密码。
- 密码登录本身不要求安装 MinIO。只有选择文件上传作为受保护业务验证时,才要先完成 MinIO 安装和联调。
2. 请求地址怎么写¶
令 BASE_URL 表示实际网关地址,如开发机实际监听的 HTTP 地址或部署后的 HTTPS 域名。下面使用项目约定的 /user 网关前缀;必须核对 Nacos 路由是否把它转发并去掉该前缀。远端地址、端口和路由值以运行环境为准。
| 操作 | 方法 | 网关外部路径 |
|---|---|---|
| 注册 | POST | /user/auth/register |
| App 密码登录 | POST | /user/auth/mobile/login/password |
| App 刷新 Token | POST | /user/auth/mobile/token/refresh |
| App 退出 | POST | /user/auth/mobile/logout |
这些 JSON 入口不需要先携带 Bearer Token,但仍要分别校验参数、密码或 Refresh Token。Reqable 中关闭这些请求继承的全局 Authorization,避免自动附带旧 Token。
BASE_URL 只是本文对实际入口的简称,不是可以直接发送的域名,也不是要求新增后端配置键。比如确认网关监听本机某端口后,将它换成 http://127.0.0.1:实际端口;公网使用实际 HTTPS 域名。地址、路径、请求方法必须同时正确,不要把端口示例当作已经确认的服务器配置。
直接访问 user 时,Controller 路径没有 /user 前缀;不要因此绕过公网网关的正常部署入口。浏览器协议流程还涉及 issuer、回调和转发头,不应随意混用直连地址。
3. 注册¶
在 Reqable 新建 POST 请求,URL 为 BASE_URL/user/auth/register,Body 选择 JSON:
{
"username": "alice",
"password": "<符合规则的用户自设密码>"
}
请求头为 Content-Type: application/json。占位符不是实际测试密码。
RegisterDto 校验用户名 3~64 个字符,密码 8~72 个字符且 UTF-8 编码不超过 72 字节。中文等多字节字符会让“字符数”和“字节数”不同。
UserServiceImpl.register 在事务中检查用户名、使用共享的 PasswordEncoder 生成 BCrypt 哈希、插入用户并插入默认角色关联。当前代码约定默认角色 ID 为 2,需要业务数据库准备对应角色;并发重复注册的最终防线是数据库用户名唯一约束,不能只靠查询预检查。
注册成功返回 ResponseResult,但不会自动登录或返回 Token。用户名冲突为 HTTP 409,格式/校验错误为 400;生产环境仍需配合限流或风控降低账号枚举风险。
检查方法:看 Reqable 的 HTTP 状态和 JSON,成功时 data 为“用户注册成功”,不是 Token。重复提交同一用户名应返回冲突;想重新测试成功路径就换一个学习用户名,不删除或修改已有真实账号。
4. 第一方 App 密码登录¶
POST BASE_URL/user/auth/mobile/login/password,仍发送 JSON:
{
"username": "alice",
"password": "<注册时设置的密码>"
}
请求只有用户名和密码,不提交 userId、角色、client secret 或认证类型。登录参数需要非空,用户名最长 64 个字符,密码最长 72 个字符且 UTF-8 不超过 72 字节;新账号必须先满足注册规则。
处理过程:
MobileAuthController
→ MobileAuthServiceImpl
→ AuthenticationManager → DaoAuthenticationProvider
→ CustomUserDetailsService 查询用户与角色
→ PasswordEncoder 校验密码
→ 返回带 userId 的 AuthenticatedUser
→ 复用身份快照创建移动会话,统一签发 RSA JWT
用户查询和角色 JOIN 已在认证过程中完成,移动端 Service 不再重复按用户名查询,也不自己比较密码。
成功响应的 data 包含以下字段:
| 字段 | 用途 |
|---|---|
tokenType |
固定 Bearer |
accessToken |
短期 RSA JWT,用于调用业务接口 |
expiresIn |
Access Token 剩余有效秒数 |
refreshToken |
高熵不透明随机凭据,只用于刷新或退出 |
refreshExpiresIn |
当前移动会话剩余有效秒数 |
有效时间读取实际响应,不在客户端假定固定秒数。密码错误和用户不存在统一返回 HTTP 401 与相同提示。
Reqable 操作顺序:复制注册请求为一个新请求 → 改成上面的登录路径 → 保持 POST 和 JSON → 输入同一账号密码 → 发送 → 在响应 Body 的 data 对象中找到两种 Token。不要只看到 HTTP 200 就把整个 JSON 当成令牌。
若用变量保存令牌,使用 Reqable 的本机私有环境/变量能力并确认未共享或同步到公开集合;具体菜单以所用版本为准。手工复制调试时也不要把 Token 粘进 URL、文档或截图。业务请求的 Authorization 只使用 accessToken 字段值,不包含 JSON 双引号,也不重复写两个 Bearer。
5. 业务请求与刷新¶
调用受保护业务接口时添加:
Authorization: Bearer <本次登录返回的 accessToken>
不把 Refresh Token 当 Bearer Token。网关和下游资源服务都验证 Access JWT,具体业务还要检查角色、scope 和对象权限。
刷新时 POST BASE_URL/user/auth/mobile/token/refresh:
{
"refreshToken": "<最近一次成功响应中的 refreshToken>"
}
每次成功刷新都返回新的一对 Access/Refresh Token。客户端必须更新保存的两者;同一会话的并发 401 应合并成一次刷新,不能多个请求同时重用旧 Refresh Token。
服务端只保存 Refresh Token 摘要,轮换后旧 Token 重放会撤销整个会话家族。家族是一次密码登录所建立的一串轮换记录;有效期从首次登录开始计算,不会每次刷新无限延期。
刷新还会按稳定用户 ID 重新读取当前账号和角色。已删除的用户不能继续刷新,角色变更反映在新 Token 中,但此前已签发的 Access JWT 不会因此立即失效。
6. 退出¶
POST BASE_URL/user/auth/mobile/logout,JSON 与刷新相同。服务端按家族撤销移动会话,客户端清理本地凭据。
退出幂等:有效格式的未知、过期或已撤销 Token 也返回成功,不让调用者通过响应探测凭据状态;空值、超长或错误 JSON 仍可能返回 400。
退出阻止继续刷新,但已签发的 Access JWT 仍可能有效到 exp。不要把此行为描述成所有业务请求立即失效。
7. Web/H5 为什么另走授权码流程¶
纯浏览器公开客户端使用下面的顺序:
浏览器打开 /user/oauth2/authorize(含 PKCE challenge)
→ 未登录时显示 /user/login 页面
→ 表单提交 username、password、CSRF Token,并携带会话 Cookie
→ 回调地址收到授权码 code
→ POST /user/oauth2/token,用 code 和 code_verifier 换 Token
/user/login 是表单认证入口,不是直接返回 JWT 的 JSON 登录接口。授权码流程需要短期浏览器 Session 和 CSRF,不能全局关闭。公开客户端不保存 client secret;带受控后端的 Web 应用可以按机密客户端或 BFF 设计。
hhjava-spa 是注册的 OAuth2 客户端标识,不是微服务名。OIDC 的 ID Token 供客户端确认登录身份,不能替代调用 API 的 Access Token。
第一方 App 使用前面的原生 JSON 接口,不要求用户操作这个浏览器流程。手机号验证码、微信和 Apple 登录属于后续扩展,当前并未因为保留 OAuth2/OIDC 就自动获得这些功能。
这里 SPA 指 Single-Page Application(单页应用),OIDC 指 OpenID Connect,BFF 指 Backend for Frontend(服务于前端的后端)。浏览器 Session 与 App 的 Refresh Token 家族是不同会话;退出 App 不等于自动退出浏览器网站。
授权弹窗是客户端是否需要用户同意某项授权的界面,登录页则用于验证用户身份,两者不是同一件事。是否出现弹窗取决于客户端、权限与同意策略,不能根据“界面少一步”判断安全性或技术先进程度。
8. 调试检查顺序¶
- 确认 gateway/user 已启动,Nacos 路由和数据库可用;这些属于实际环境验证。
- 注册测试账号,再进行密码登录;不要在日志、公开环境文件或截图中保留凭据。
- 保存响应 Token 到 Reqable 私有变量;用 Access Token 调用当前代码定义的受保护接口。
- 用最新 Refresh Token 刷新并更新变量;正常验证不要误用旧 Token。
- 退出后再尝试刷新,检查失败契约。重放撤销测试使用独立测试会话。
- 遇到 400 查请求格式,401 查认证凭据,403 查授权规则,404 查路径/路由;500 再结合脱敏日志排查服务端故障。
线上通信使用 HTTPS;移动端使用系统安全存储保存会话凭据,不长期保存原始密码。