AOVIS App 登录认证接口规范
Authentication API Specification for Mobile App
版本 1.0 | 2026-04-13 | 适用对象:App 研发团队
注意:App Token 与网页端 Session 完全独立,同一用户可以同时在网页端登录和 App 登录,互不干扰。
Token 格式前缀为aovis_app_,长度约 100 字符,请按字符串存储,不要做 JWT 解码。
1. 概述
AOVIS App 使用独立于网页端的 Token 体系与后端通信。网页端(aovis.app)采用 Auth.js Cookie Session,原生 App 不使用 Cookie,改用数据库存储的随机 Bearer Token(以下简称 App Token)。
App Token 的完整生命周期如下:
- 用户在 App 内完成登录(Google / Apple / Email Magic Link 三选一)
- App 调用颁发接口,后端验证身份后签发 App Token
- App 将 Token 存入 Keychain(iOS)或 Keystore(Android)
- 后续所有 API 请求在 Authorization Header 中携带 Token
- 用户退出登录时调用吊销接口,Token 立即失效
2. Base URL 与通用规范
| 项目 | 值 |
|---|---|
| Base URL(生产) | https://aovis.app |
| Content-Type | application/json |
| 字符编码 | UTF-8 |
| 鉴权方式 | Authorization: Bearer <app_token> |
| Token 有效期 | 30 天(到期前需换新) |
| Magic Link 有效期 | 10 分钟,一次性使用 |
3. 登录流程总览
App 支持三种登录方式,最终都会获得一个 App Token。三条路径的起点不同,终点相同。
| 登录方式 | 适用场景 | App 端操作 | 后端接口 |
|---|---|---|---|
| Google OAuth | 用户有 Google 账号 | 调用 Google Sign-In SDK,获取 ID Token | POST /api/auth/app-token |
| Apple OAuth | iOS 用户,Sign in with Apple | 调用 Apple Authentication SDK,获取 Identity Token | POST /api/auth/app-token |
| Email Magic Link | 任意邮箱用户(兜底路径) | 用户输入邮箱,App 请求发送邮件;用户点击邮件链接,App 拦截链接获取 token | POST /api/auth/app-magic-link + POST /api/auth/app-magic-link/verify |
4. 接口详情
4.1 POST /api/auth/app-token
用于 Google 和 Apple OAuth 路径。App 在原生 SDK 完成 OAuth 后,将平台颁发的 Identity Token 提交给后端,换取 AOVIS App Token。
请求
POST https://aovis.app/api/auth/app-token
Content-Type: application/json
{
"provider": "google" | "apple",
"identity_token": "<Google ID Token 或 Apple Identity Token>",
"platform": "ios" | "android",
"device_label": "iPhone 15 Pro" // 可选,便于后台识别设备
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| provider | string | 是 | 固定值 google 或 apple |
| identity_token | string | 是 | Google:调用 GoogleSignIn.authenticate() 后从 GoogleSignInAuthentication.idToken 获取;Apple:调用 ASAuthorizationAppleIDCredential.identityToken 获取,Base64 解码为字符串 |
| platform | string | 是 | 固定值 ios 或 android |
| device_label | string | 否 | 可选,传入设备型号字符串,后台展示用 |
成功响应 200 OK
{
"access_token": "aovis_app_xxxxxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 2592000,
"user_id": "clxxxxxxxxxxxxxxxx",
}
错误响应
| HTTP 状态 | error 字段 | 含义 |
|---|---|---|
| 401 | invalid_token | identity_token 验证失败(过期、签名错误、aud 不匹配) |
| 400 | missing_fields | 必填字段缺失 |
| 400 | invalid_provider | provider 不是 google 或 apple |
Google 路径注意事项
- identity_token 来源:GoogleSignIn SDK 的
GoogleSignInAuthentication.idToken - 后端通过 Google tokeninfo API 验证,无需 App 端做额外操作
- Google Client ID 需与后端配置一致(
AUTH_GOOGLE_ID = 495713473945-5mi26rd9glrv2qu7f6hi6aqnutl36ac2.apps.googleusercontent.com)
Apple 路径注意事项
- identity_token 是 Data 类型,需转为 UTF-8 字符串再传入 JSON
- 后端通过 Apple JWKS 公钥验证签名,无需 App 传额外参数
- Apple Bundle ID 需与后端
APPLE_CLIENT_ID环境变量一致(APPLE_CLIENT_ID = com.aovis.app) - Sign in with Apple 只在 iOS 14+ 可用,Android 无此选项
4.2 POST /api/auth/app-magic-link
Email Magic Link 路径第一步:触发发送登录邮件。用户输入邮箱后,App 调用此接口,后端发送一封包含一次性链接的邮件。
请求
POST https://aovis.app/api/auth/app-magic-link
Content-Type: application/json
{
"email": "[email protected]"
}
成功响应 200 OK
{ "sent": true }
无论邮箱是否已注册,都返回 200,防止邮箱枚举攻击。App 端直接提示「邮件已发送,请查收」即可。
错误响应
| HTTP 状态 | error 字段 | 含义 |
|---|---|---|
| 400 | invalid_email | 邮箱格式不合法 |
邮件内容
用户收到的邮件主题为 Sign in to AOVIS,邮件内有一个按钮,链接格式为:
https://aovis.app/app-auth?token=aovis_ml_xxxxxxxxxxxxxxxx
链接有效期 10 分钟,只能使用一次。
4.3 POST /api/auth/app-magic-link/verify
Email Magic Link 路径第二步:App 拦截到邮件链接中的 token 后,调用此接口完成验证,换取 App Token。
App 端拦截链接的流程
- 用户点击邮件中的链接
- 系统检测到 App 已安装且配置了 Universal Links / App Links,直接唤起 App
- App 在
onReceiveUniversalLink/onAppLinkReceived回调中获取完整 URL - 从 URL 中解析 query 参数
token
请求
POST https://aovis.app/api/auth/app-magic-link/verify
Content-Type: application/json
{
"token": "aovis_ml_xxxxxxxxxxxxxxxx",
"platform": "ios" | "android",
"device_label": "Pixel 8" // 可选
}
成功响应 200 OK
与 /api/auth/app-token 成功响应格式完全相同:
{
"access_token": "aovis_app_xxxxxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 2592000,
"user_id": "clxxxxxxxxxxxxxxxx",
}
错误响应
| HTTP 状态 | error 字段 | 含义 |
|---|---|---|
| 401 | invalid_or_expired_token | token 不存在、已过期(超过 10 分钟)、或已被使用过 |
4.4 DELETE /api/auth/app-token
退出登录。调用后当前 Token 立即失效,后续使用该 Token 的请求将返回 401。
请求
DELETE https://aovis.app/api/auth/app-token
Authorization: Bearer aovis_app_xxxxxxxxxxxxxxxx
成功响应 200 OK
{ "revoked": true }
错误响应
| HTTP 状态 | 含义 |
|---|---|
| 401 | Token 无效、已过期或已被吊销 |
5. 携带 Token 调用业务接口
登录成功后,所有需要身份验证的业务接口都通过 Authorization Header 传递 Token:
GET https://aovis.app/api/v1/devices
Authorization: Bearer aovis_app_xxxxxxxxxxxxxxxx
Content-Type: application/json
如果 Token 无效或过期,接口统一返回:
HTTP 401 Unauthorized
{
"error": "unauthorized"
}
收到 401 后,App 应清除本地存储的 Token,跳转到登录页面,引导用户重新登录。
6. Universal Links / App Links 配置
为使 Email Magic Link 能直接唤起 App,需要在 App 端完成以下配置。后端已部署所需的验证文件,App 端只需声明对应 domain。
6.1 iOS — Universal Links
在 Xcode 中启用 Associated Domains
- 打开项目 Target → Signing & Capabilities
- 点击 + 添加 Associated Domains
- 添加条目:
applinks:aovis.app
处理链接回调
// AppDelegate.swift
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
url.host == "aovis.app",
url.path.hasPrefix("/app-auth") else { return false }
let token = URLComponents(url: url, resolvingAgainstBaseURL: false)
?.queryItems?.first(where: { $0.name == "token" })?.value
// 将 token 传递给登录模块
NotificationCenter.default.post(name: .magicLinkReceived, object: token)
return true
}
6.2 Android — App Links
在 AndroidManifest.xml 中声明 intent-filter
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="aovis.app"
android:pathPrefix="/app-auth" />
</intent-filter>
</activity>
在 Activity 中处理 Intent
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val uri = intent.data ?: return
if (uri.host == "aovis.app" && uri.path?.startsWith("/app-auth") == true) {
val token = uri.getQueryParameter("token")
// 将 token 传递给登录 ViewModel
}
}
联调前需将正式发布证书的 SHA-256 指纹告知 AOVIS 后端团队,后端将更新 https://aovis.app/.well-known/assetlinks.json 中的指纹配置。开发阶段可先用调试证书指纹联调,发布前替换为正式证书指纹。
7. 错误码汇总
| HTTP 状态 | error 字段 | 触发场景 | App 端建议处理 |
|---|---|---|---|
| 400 | missing_fields | 请求体缺少必填字段 | 检查请求构造逻辑 |
| 400 | invalid_email | 邮箱格式不合法 | 在 UI 层提前做格式校验 |
| 400 | invalid_provider | provider 字段值不合法 | 检查请求构造逻辑 |
| 401 | invalid_token | Google/Apple identity_token 验证失败 | 引导用户重新进行 OAuth 流程 |
| 401 | invalid_or_expired_token | Magic Link token 无效、过期或已使用 | 提示用户重新请求邮件 |
| 401 | unauthorized | App Token 无效或过期 | 清除本地 Token,跳转登录页 |
| 500 | — | 服务器内部错误 | 展示通用错误提示,可重试 |
8. 联调步骤建议
建议按以下顺序完成接入联调,每步验证通过后再进入下一步:
| 步骤 | 验证内容 | 工具 |
|---|---|---|
| Step 1 | POST /api/auth/app-token(Google):使用 Google OAuth Playground 获取测试 ID Token,通过 Postman 调用接口,确认返回 200 + access_token | Google OAuth Playground + Postman |
| Step 2 | 携带 access_token 调用任意业务接口(如 GET /api/v1/devices),确认鉴权通过返回 200 | Postman |
| Step 3 | DELETE /api/auth/app-token 吊销 Token,再次用该 Token 调业务接口确认返回 401 | Postman |
| Step 4 | POST /api/auth/app-magic-link 发送邮件,确认收到邮件且链接格式正确 | Postman + 真实邮箱 |
| Step 5 | POST /api/auth/app-magic-link/verify 提交从邮件链接中提取的 token,确认返回 200 + access_token | Postman |
| Step 6 | App 端集成 Universal Links / App Links,真机点击邮件链接确认 App 被唤起且 token 正确获取 | 真机测试 |
| Step 7 | POST /api/auth/app-token(Apple):iOS 真机完成 Sign in with Apple 流程,确认返回 200 + access_token | iOS 真机 |
Fifth Key Element Co., Limited | AOVIS Internal Confidential | Version 1.0 | 2026-04-13