63 lines
3.0 KiB
Markdown
63 lines
3.0 KiB
Markdown
|
|
# Skill:登录与注册机制
|
|||
|
|
|
|||
|
|
与具体业务无关的认证设计:服务端 API 与客户端 Android 的登录、注册、令牌与鉴权约定。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 服务端(API)
|
|||
|
|
|
|||
|
|
### 认证方式
|
|||
|
|
|
|||
|
|
- **访问令牌(Access Token)**:JWT,用于接口鉴权,默认有效期 2 小时(`ACCESS_TOKEN_EXPIRE_MINUTES`)。
|
|||
|
|
- **刷新令牌(Refresh Token)**:随机字符串,存库,默认 30 天有效;用于在访问令牌过期后换取新访问令牌。
|
|||
|
|
|
|||
|
|
### 密码
|
|||
|
|
|
|||
|
|
- 使用 **bcrypt** 哈希存储,不存明文。
|
|||
|
|
- 注册/登录时由 `auth_service.hash_password` / `verify_password` 处理。
|
|||
|
|
|
|||
|
|
### 登录/注册入口
|
|||
|
|
|
|||
|
|
- 手机号 + 密码:`POST /api/auth/login`、`POST /api/auth/register`。
|
|||
|
|
- 手机号 + 短信验证码:`POST /api/auth/login/sms`(未注册则自动注册,需提供昵称)。
|
|||
|
|
- 所有入口均要求请求体中 `agreed_to_terms == true`,否则 400。
|
|||
|
|
|
|||
|
|
### 令牌刷新
|
|||
|
|
|
|||
|
|
- `POST /api/auth/refresh`,Body: `{ "refresh_token": "..." }`。
|
|||
|
|
- 校验:存在、未撤销、未过期、对应用户存在;通过后返回新的 `access_token`,`refresh_token` 原样返回(不轮换)。
|
|||
|
|
|
|||
|
|
### 鉴权依赖
|
|||
|
|
|
|||
|
|
- 需要登录的接口:`Depends(get_current_user)`,从 `Authorization: Bearer <access_token>` 解析 JWT,校验 `type == "access"` 并加载用户。
|
|||
|
|
- 可选登录:`Depends(get_optional_user)`,无 token 或无效时返回 `None`。
|
|||
|
|
|
|||
|
|
### 路由与安全
|
|||
|
|
|
|||
|
|
- 认证相关路由统一前缀:`/api/auth`(如 login、register、refresh、logout 等)。
|
|||
|
|
- OAuth2 约定:`OAuth2PasswordBearer(tokenUrl="/api/auth/login")`,仅用于声明从哪里取 token,实际登录仍用上述接口。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 客户端(Android)
|
|||
|
|
|
|||
|
|
### 令牌存储
|
|||
|
|
|
|||
|
|
- 使用 **DataStore Preferences**(`TokenPreferences`)存 `access_token`、`refresh_token`、`user_id`。
|
|||
|
|
- 通过单例 **TokenManager** 读写;提供 `initialize(context)`、`saveTokens`、`getAccessToken`/`getRefreshToken`(含 suspend 与 sync 版本)、`clearTokens`。
|
|||
|
|
|
|||
|
|
### 登录状态
|
|||
|
|
|
|||
|
|
- TokenManager 维护 `isLoggedIn`(有有效 access_token 即为 true),并提供 `rememberIsLoggedIn()` 供 Compose 使用。
|
|||
|
|
- 登出或刷新失败时调用 `clearTokens()` 并 `notifyTokenRefreshFailed()`,由回调统一处理(如清栈并跳转登录页)。
|
|||
|
|
|
|||
|
|
### 请求头与刷新
|
|||
|
|
|
|||
|
|
- 使用 **AuthInterceptor**(Ktor Client 插件):请求前自动附加 `Authorization: Bearer <access_token>`。
|
|||
|
|
- 收到 401 时:用当前 refresh_token 调 `AuthService.refreshToken()`;成功则写回新 token,失败则 `clearTokens()` + `notifyTokenRefreshFailed()`,由 App 层跳转登录。
|
|||
|
|
|
|||
|
|
### 登录成功/登出与导航
|
|||
|
|
|
|||
|
|
- 登录成功:由各登录入口回调 `onLoginSuccess`,主流程将 `isLoggedIn = true` 并导航到主界面(如会话列表),并 `popUpTo(Login)` 清掉登录页。
|
|||
|
|
- 登出或刷新失败:`onLogout` / TokenManager 回调中执行 `navController.navigate(Screen.Login.route) { popUpTo(0) { inclusive = true } }`,保证回到登录且无法返回。
|