认证授权指南
NexusBook API 使用 OAuth2 和 OIDC 标准进行认证和授权,提供安全可靠的访问控制机制。
域名规划
NexusBook 采用基于功能分离的域名架构,将认证协议层、开放业务 API 和管理 API 部署在不同的域名下,以提供更好的安全性和可维护性。
认证授权中心 (auth.nexusbook.app)
用途:OAuth2/OIDC 标准协议端点
此域名专门用于 OAuth2 和 OIDC 核心协议端点,所有接口直接挂载在根路径下:
# OIDC 发现端点
GET https://auth.nexusbook.app/.well-known/openid-configuration
# JWKS 公钥端点
GET https://auth.nexusbook.app/jwks.json
# 授权端点
GET https://auth.nexusbook.app/authorize
# 令牌端点
POST https://auth.nexusbook.app/token
# 用户信息端点
GET https://auth.nexusbook.app/userinfo
注意:根据部署规范,auth.nexusbook.app 域名下的 API 接口不包含 /auth 子路径,所有接口直接挂载在根路径下。
开放业务 API (open.nexusbook.app)
用途:面向最终用户和第三方应用的业务接口
用户认证与管理
# 用户注册
POST https://open.nexusbook.app/api/v1/auth/register
# 用户登录(支持多种方式:邮箱+密码、手机+验证码)
POST https://open.nexusbook.app/api/v1/auth/login
# 会话管理
GET https://open.nexusbook.app/api/v1/auth/sessions
DELETE https://open.nexusbook.app/api/v1/auth/sessions/{sessionId}
API 密钥管理
# 创建 API Key
POST https://open.nexusbook.app/api/v1/api-keys
# 列出 API Keys
GET https://open.nexusbook.app/api/v1/api-keys
# 吊销 API Key
POST https://open.nexusbook.app/api/v1/api-keys/{keyId}/revoke
OAuth 客户端管理
# 创建 OAuth 客户端
POST https://open.nexusbook.app/api/v1/oauth/clients
# 重新生成密钥
POST https://open.nexusbook.app/api/v1/oauth/clients/{clientId}/regenerate-secret
第三方登录
# 列出可用的第三方登录选项(公开)
GET https://open.nexusbook.app/api/v1/oidc/providers
# 获取 OAuth 登录 URL
GET https://open.nexusbook.app/api/v1/auth/oauth/{provider}/authorize
# OAuth 回调处理
POST https://open.nexusbook.app/api/v1/auth/oauth/{provider}/callback
OIDC 提供商管理(管理员)
# 列出所有 OIDC 提供商配置
GET https://open.nexusbook.app/api/v1/admin/oidc/providers
# 添加 OIDC 提供商
POST https://open.nexusbook.app/api/v1/admin/oidc/providers
# 更新 OIDC 提供商
PATCH https://open.nexusbook.app/api/v1/admin/oidc/providers/{providerId}
# 删除 OIDC 提供商
DELETE https://open.nexusbook.app/api/v1/admin/oidc/providers/{providerId}
域名总览
| 域名 | 路径前缀 | 用途 | 权限 |
|---|---|---|---|
auth.nexusbook.app |
/ |
OAuth2/OIDC 核心协议 | 公开 |
open.nexusbook.app |
/api/v1/auth |
用户认证与管理 | 用户自助 |
open.nexusbook.app |
/api/v1/api-keys |
API 密钥管理 | 用户自助 |
open.nexusbook.app |
/api/v1/oauth/clients |
OAuth 客户端管理 | 开发者自助 |
open.nexusbook.app |
/api/v1/oidc/providers |
第三方登录查询 | 公开 |
open.nexusbook.app |
/api/v1/auth/oauth |
第三方 OAuth 登录 | 公开 |
open.nexusbook.app |
/api/v1/admin/oidc/providers |
OIDC 提供商管理 | 管理员 |
OAuth2 认证流程
客户端凭证流程(Client Credentials Flow)
适用场景:服务端应用、后台任务、系统集成
这是最简单的认证方式,适合不需要用户交互的场景。
流程图
┌─────────┐ ┌──────────┐
│ 客户端 │ │ 授权服务器 │
└────┬────┘ └────┬─────┘
│ │
│ POST /token │
│ grant_type=client_credentials │
│ client_id=xxx │
│ client_secret=xxx │
│ scope=doc:read data:write │
├───────────────────────────────────────────>│
│ │
│ access_token │
│<───────────────────────────────────────────┤
│ │
示例代码
cURL:
curl -X POST https://auth.nexusbook.app/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'client_id=your_client_id' \
-d 'client_secret=your_client_secret' \
-d 'scope=doc:read data:read data:write'
JavaScript (Node.js):
const axios = require('axios');
const qs = require('querystring');
async function getAccessToken() {
const response = await axios.post(
'https://auth.nexusbook.app/token',
qs.stringify({
grant_type: 'client_credentials',
client_id: 'your_client_id',
client_secret: 'your_client_secret',
scope: 'doc:read data:read data:write'
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
return response.data.access_token;
}
Python:
import requests
def get_access_token():
response = requests.post(
'https://auth.nexusbook.app/token',
data={
'grant_type': 'client_credentials',
'client_id': 'your_client_id',
'client_secret': 'your_client_secret',
'scope': 'doc:read data:read data:write'
}
)
return response.json()['access_token']
授权码流程(Authorization Code Flow)
适用场景:Web 应用、移动应用(需要用户授权)
这是最安全的认证方式,适合需要用户授权的场景。
流程图
┌─────────┐ ┌──────────┐ ┌──────────┐
│ 用户 │ │ 客户端 │ │ 授权服务器 │
└────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 访问应用 │ │
├───────────────────>│ │
│ │ 重定向到授权页面 │
│ ├────────────────────>│
│ │ │
│ │ 显示授权页面 │
│<──────────────────────────────────────────┤
│ │ │
│ 用户授权 │ │
├────────────────────────────────────────>│
│ │ │
│ │ 重定向+授权码 │
│ │<────────────────────┤
│ │ │
│ │ 用授权码换取Token │
│ ├────────────────────>│
│ │ │
│ │ access_token │
│ │<────────────────────┤
│ │ │
步骤详解
步骤 1:引导用户到授权页面
// 构建授权 URL
const authUrl = new URL('https://auth.nexusbook.app/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your_client_id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'doc:read data:read data:write');
authUrl.searchParams.set('state', generateRandomState()); // CSRF 保护
// 重定向用户
window.location.href = authUrl.toString();
步骤 2:处理回调并换取 Token
// 在回调端点处理
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// 验证 state 防止 CSRF
if (state !== expectedState) {
return res.status(400).send('Invalid state');
}
// 用授权码换取 Token
const response = await axios.post(
'https://auth.nexusbook.app/token',
qs.stringify({
grant_type: 'authorization_code',
code: code,
client_id: 'your_client_id',
client_secret: 'your_client_secret',
redirect_uri: 'https://yourapp.com/callback'
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
const { access_token, refresh_token } = response.data;
// 保存 Token
// ...
});
OIDC 集成
发现端点
获取 OIDC 配置信息:
curl https://auth.nexusbook.app/.well-known/openid-configuration
响应示例:
{
"issuer": "https://auth.nexusbook.app",
"authorization_endpoint": "https://auth.nexusbook.app/authorize",
"token_endpoint": "https://auth.nexusbook.app/token",
"userinfo_endpoint": "https://auth.nexusbook.app/userinfo",
"jwks_uri": "https://auth.nexusbook.app/jwks.json",
"scopes_supported": ["openid", "profile", "email", "doc:read", "data:write"],
"response_types_supported": ["code", "token"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"]
}
JWKS 端点
获取 JWT 验证公钥:
curl https://auth.nexusbook.app/jwks.json
UserInfo 端点
获取当前用户信息:
curl https://auth.nexusbook.app/userinfo \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Token 管理
Token 结构
NexusBook 使用 JWT (JSON Web Token) 格式:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9. # Header
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I... # Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQss... # Signature
解码后的 Payload:
{
"sub": "user-12345",
"client_id": "your_client_id",
"scope": "doc:read data:write",
"iat": 1638360000,
"exp": 1638363600
}
Token 存储
安全存储建议:
Web 应用:使用 HttpOnly Cookie
res.cookie('access_token', token, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000 // 1 小时 });移动应用:使用安全存储(iOS Keychain、Android Keystore)
服务端应用:环境变量或加密配置文件
不安全的做法:
- ❌ 存储在 localStorage
- ❌ 存储在普通 Cookie(非 HttpOnly)
- ❌ 硬编码在代码中
- ❌ 明文保存在数据库
Token 刷新
使用 Refresh Token 获取新的 Access Token:
curl -X POST https://auth.nexusbook.app/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token' \
-d 'refresh_token=YOUR_REFRESH_TOKEN' \
-d 'client_id=your_client_id' \
-d 'client_secret=your_client_secret'
自动刷新示例:
class TokenManager {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = null;
this.refreshToken = null;
this.expiresAt = null;
}
async getAccessToken() {
// 如果 Token 还有效,直接返回
if (this.accessToken && Date.now() < this.expiresAt - 60000) {
return this.accessToken;
}
// Token 过期,使用 refresh_token 刷新
if (this.refreshToken) {
await this.refreshAccessToken();
return this.accessToken;
}
// 没有 Token,获取新的
await this.fetchNewToken();
return this.accessToken;
}
async refreshAccessToken() {
const response = await axios.post('https://auth.nexusbook.app/token', {
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
client_id: this.clientId,
client_secret: this.clientSecret
});
this.updateTokens(response.data);
}
updateTokens(data) {
this.accessToken = data.access_token;
this.refreshToken = data.refresh_token || this.refreshToken;
this.expiresAt = Date.now() + (data.expires_in * 1000);
}
}
Scope 权限详解
权限级别
| Scope | 权限 | 说明 |
|---|---|---|
doc:read |
文档读取 | 读取文档属性、元数据、视图、设置 |
doc:write |
文档写入 | 创建、更新、删除文档和元数据 |
data:read |
数据读取 | 查询数据行 |
data:write |
数据写入 | 创建、更新、删除数据行 |
views:manage |
视图管理 | 创建、更新、删除视图 |
comments:write |
评论管理 | 创建、更新、删除评论 |
approvals:manage |
审批管理 | 发起和处理审批流程 |
requests:manage |
请求管理 | 创建、合并、关闭变更请求 |
webhooks:manage |
Webhook 管理 | 创建、更新、删除 Webhook |
权限组合
只读访问:
scope=doc:read data:read
数据管理:
scope=doc:read data:read data:write
完全访问:
scope=doc:read doc:write data:read data:write views:manage comments:write approvals:manage requests:manage
API Keys 管理
API 密钥用于服务集成与自动化任务,支持最小权限与可撤销。
创建 API Key
curl -X POST 'https://auth.nexusbook.app/api-keys' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "CI Pipeline",
"scopes": ["doc:read", "data:read"],
"expiresAt": "2026-01-01T00:00:00Z"
}'
吊销 API Key
curl -X POST 'https://auth.nexusbook.app/api-keys/{keyId}/revoke' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
最佳实践
- 使用最小权限的 scopes
- 配置过期时间与 IP 白名单
- 开启使用日志与速率限制
两步验证(2FA/MFA)
支持 TOTP(Authenticator)、短信、邮件与备用码。
- 开启:
POST /auth/2fa/enable(返回二维码与密钥) - 验证:
POST /auth/2fa/verify - 禁用:
POST /auth/2fa/disable
会话管理
- 列出会话:
GET /auth/sessions - 关闭会话:
DELETE /auth/sessions/{sessionId} - 强制退出所有:
POST /auth/sessions/logout-all
1. 使用 HTTPS
始终使用 HTTPS 传输敏感信息:
// ✅ 正确
const apiUrl = 'https://open.nexusbook.app/api/v1/...';
// ❌ 错误
const apiUrl = 'http://open.nexusbook.app/api/v1/...';
2. Scope 最小权限原则
只请求应用需要的最小权限:
// ✅ 正确 - 只读应用只请求读权限
scope: 'doc:read data:read'
// ❌ 错误 - 请求了不必要的写权限
scope: 'doc:read doc:write data:read data:write'
3. 保护 Client Secret
- ✅ 使用环境变量
- ✅ 使用密钥管理服务(AWS Secrets Manager、Azure Key Vault)
- ❌ 不要提交到代码仓库
- ❌ 不要在客户端代码中使用
安全最佳实践
function verifyToken(token) { return new Promise((resolve, reject) => { jwt.verify(token, getKey, { audience: 'your_client_id', issuer: 'https://auth.nexusbook.app', algorithms: ['RS256'] }, (err, decoded) => { if (err) reject(err); else resolve(decoded); }); }); }
### 5. 实施 Rate Limiting
保护您的应用免受滥用:
```javascript
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100 // 限制 100 个请求
});
app.use('/api/', limiter);
错误处理
常见认证错误
| 错误码 | 说明 | 解决方案 |
|---|---|---|
INVALID_CLIENT |
客户端凭证错误 | 检查 client_id 和 client_secret |
INVALID_TOKEN |
Token 无效或过期 | 刷新或重新获取 Token |
UNAUTHORIZED |
未授权 | 确保请求头包含有效 Token |
FORBIDDEN |
权限不足 | 请求包含所需权限的 Scope |
INVALID_SCOPE |
Scope 无效 | 检查 Scope 拼写和可用性 |
错误处理示例
async function callApi(endpoint, options = {}) {
try {
const token = await tokenManager.getAccessToken();
const response = await axios.get(endpoint, {
headers: {
'Authorization': `Bearer ${token}`,
...options.headers
}
});
return response.data;
} catch (error) {
if (error.response) {
const { code, message } = error.response.data;
// Token 过期,刷新后重试
if (code === 'INVALID_TOKEN') {
await tokenManager.refreshAccessToken();
return callApi(endpoint, options); // 重试
}
// 权限不足
if (code === 'FORBIDDEN') {
throw new Error('权限不足,请联系管理员');
}
// 其他错误
throw new Error(message.zh || message.en || '未知错误');
}
throw error;
}
}