本页列出身份与授权服务的全部端点。分为三组:应用管理(/api/v1,浏览器调用)、应用成员组管理(/oauth,应用后端用 client_credentials 调用)、OAuth 协议端点(/oauth,授权 / 换 token / userinfo)。协议端点的参数与响应细节见授权码流程。
通用约定#
- 路径前缀:应用管理面
/api/v1,协议面与管理面/oauth(无前缀)。 - 字段命名 snake_case,时间为 ISO 8601(UTC)。
- 错误形状:管理面
{ "detail": "中文说明" },协议面{ "error": "…", "error_description": "…" }。 - 分页响应统一为
{ items, total, page, per_page, pages }。
认证方式#
| 面 | 调用方 | 认证 |
|---|---|---|
应用管理(/api/v1) | 浏览器 | 平台登录态(session cookie) |
OAuth 协议(/oauth/token) | 应用后端 | client_id + client_secret(HTTP Basic 或表单二选一) |
应用成员组管理(/oauth/* 管理 API) | 应用后端 | Authorization: Bearer <client_credentials token> |
userinfo | 应用后端 | Authorization: Bearer <access_token> |
jwks / discovery | 任意 | 公开 |
一、应用管理#
创建应用#
群创建者操作。免审核,创建即激活。enable_client_credentials=true 时自动附带管理 scope。响应中 包含一次性 client_secret,请立即保存。
POST /api/v1/forums/{slug}/oauth-applications
请求体:
{
"name": "示例应用",
"description": "应用用途说明",
"logo_url": "https://app.example.com/logo.png",
"redirect_uri": "https://app.example.com/callback",
"scopes": ["openid", "profile", "email", "group", "member_groups"],
"enable_client_credentials": true
}
响应 201(节选,client_secret 仅本次返回):
{
"id": 3,
"client_id": "app_9f8e7d6c5b4a3f2e1d0c",
"client_secret": "jX3kQ9mN2vB7cL4pR8tW1yZ6aE0dF5gH",
"name": "示例应用",
"redirect_uri": "https://app.example.com/callback",
"scopes": ["openid", "profile", "email", "group", "member_groups", "members:read", "member_groups:write"],
"grant_types": ["authorization_code", "client_credentials"],
"status": "active"
}
| 状态码 | 含义 |
|---|---|
| 201 | 创建成功并激活 |
| 400 | scope 不合法或回调地址不合法 |
| 403 | 调用者不是群创建者 |
查看本群应用#
GET /api/v1/forums/{slug}/oauth-applications
群创建者与管理员可调用,返回 OAuthApplicationOut 数组。
编辑 / 移除 / 重置 Secret#
PATCH /api/v1/forums/{slug}/oauth-applications/{id} # 编辑名称、描述、Logo、回调、scope、client_credentials
DELETE /api/v1/forums/{slug}/oauth-applications/{id} # 移除应用,吊销全部 token
POST /api/v1/forums/{slug}/oauth-applications/{id}/rotate-secret # 重置 secret,一次性返回,旧值立即失效
回调地址与 scope 的变更对 新发起的授权 生效,已签发的 token 不受影响。
管理员:禁用 / 恢复#
GET /api/v1/admin/oauth-applications?status_filter=active # 列表,可过滤 active / disabled
POST /api/v1/admin/oauth-applications/{id}/disable # 禁用,吊销该应用全部 token
POST /api/v1/admin/oauth-applications/{id}/enable # 恢复被禁用的应用
列表端点返回 AdminOAuthApplicationOut 数组(非分页信封),按创建时间倒序,默认 50 条。
用户撤销授权#
GET /api/v1/me/oauth-grants # 我授权过的应用列表
DELETE /api/v1/me/oauth-grants/{client_id} # 撤销授权,吊销该应用名下我的 token
二、应用成员组管理(client_credentials)#
先换取管理凭据(此步无用户参与):
curl -X POST https://mooc.pneumas.org/oauth/token \
-u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \
-d grant_type=client_credentials \
-d scope="members:read member_groups:write"
{ "access_token": "…", "token_type": "Bearer", "expires_in": 3600, "scope": "members:read member_groups:write" }
此后所有管理接口携带 Authorization: Bearer <cc_token>。上下文固定为应用绑定的群,无需也不能传群 ID;访问其他群的数据一律返回 404,不暴露存在性。
应用成员组 CRUD#
| 方法 | 路径 | 所需 scope | 说明 |
|---|---|---|---|
| GET | /oauth/member-groups | members:read | 全部成员组,含默认组 |
| POST | /oauth/member-groups | member_groups:write | 创建;请求体 {name, description?, sort?, is_default?};同名 409 |
| PATCH | /oauth/member-groups/{id} | member_groups:write | 修改名称 / 描述 / 排序 / 设默认;默认组不可取消默认标记 |
| DELETE | /oauth/member-groups/{id} | member_groups:write | 默认组不可删;组内仍有成员 → 409;组映射了课程 → 409 |
创建示例:
curl -X POST https://mooc.pneumas.org/oauth/member-groups \
-H "Authorization: Bearer $CC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "高级会员", "description": "可访问进阶内容"}'
{
"id": 12,
"forum_id": 7,
"name": "高级会员",
"description": "可访问进阶内容",
"sort": 0,
"is_default": false,
"member_count": 1,
"member_ids": [10042]
}
群成员列表#
GET /oauth/members?page=1&per_page=20 # 所需 scope:members:read
page 默认 1,per_page 默认 20、范围 1–100。返回分页信封:
{
"items": [
{
"user_id": 10042,
"username": "user_10042",
"display_name": "用户 10042",
"avatar_url": null,
"member_group_ids": [11, 12],
"joined_at": "2026-08-17T12:00:00+00:00"
}
],
"total": 1, "page": 1, "per_page": 20, "pages": 1
}
成员应用成员组分配#
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /oauth/members/{user_id}/member-groups/{group_id} | 将 已有群成员 加入应用成员组;非群成员 → 409,不会自动拉人进群 |
| DELETE | /oauth/members/{user_id}/member-groups/{group_id} | 移出应用成员组;该用户的最后一个应用成员组 → 409,需改用 /move |
| POST | /oauth/members/{user_id}/member-groups/{group_id}/move | 表单字段 target_group_id,原子地从一个应用成员组移到另一个 |
将用户 10042 从默认应用成员组(11)移到"高级会员"(12):
curl -X POST https://mooc.pneumas.org/oauth/members/10042/member-groups/11/move \
-H "Authorization: Bearer $CC_TOKEN" \
-d target_group_id=12
写操作会同步平台侧的课程权益:成员应用成员组变更后,对应课程的开通与收回随之更新。在应用侧修改应用成员组,平台群管理页面立即可见。
成员准入只在群管理:管理 API 只能操作 已有群成员 的应用成员组分配,不能把非成员拉进群。新成员加入群由群创建者 / 管理员在平台侧完成。
三、OAuth 协议端点#
| 端点 | 说明 |
|---|---|
GET /oauth/authorize | 授权码流程起点 |
POST /oauth/token | 换 token、刷新、client_credentials |
GET /oauth/userinfo | 用户身份与应用成员组 |
GET /oauth/jwks | RS256 公钥 |
GET /.well-known/openid-configuration | 发现端点 |
参数与响应定义见授权码流程。
错误码#
协议面(authorize / token / userinfo)#
| error | HTTP | 出现条件 |
|---|---|---|
invalid_request | 400 | 参数缺失、非法或超长 |
invalid_client | 400 | client_id / client_secret 错误,或应用未激活 / 被禁用 |
invalid_grant | 400 | code 已用 / 过期 / 不匹配;refresh 失效;PKCE 校验失败 |
unauthorized_client | 400 | 客户端未被授权使用该 grant_type |
invalid_scope | 400 | 申请了未获批或白名单外的 scope |
insufficient_scope | 403 | 管理 API 的 token 缺少所需 scope |
invalid_token | 401 | userinfo / 管理 API 的 token 无效、过期或已吊销(携带 WWW-Authenticate 头) |
access_denied | — | 用户在同意页点了拒绝(经回调 ?error= 返回,非 HTTP 错误) |
管理面(/api/v1)#
| HTTP | 出现条件 |
|---|---|
| 400 | scope 或回调地址不合法 |
| 403 | 调用者不是群创建者 / 管理员;非管理员访问管理接口 |
| 404 | 群、应用或授权记录不存在 |
| 409 | 状态不允许该操作(如编辑被禁用的应用、重复禁用、成员组同名、删除有成员的成员组) |
