本文描述从零接入身份与授权服务的完整步骤。接入免审核,应用创建即生效。协议细节见授权码流程,端点完整定义见API 参考。
前置条件#
- 已在平台创建一个 群(小组)。任何平台用户都可以创建群。
- 你是该群的 创建者。只有群创建者可以为本群接入应用。
第一步:创建应用#
进入你的群 → 群管理 → 接入应用 页签,填写应用信息:
| 字段 | 必填 | 说明 |
|---|---|---|
| 应用名称 | 是 | 展示在同意页,用户据此识别应用 |
| 描述 | 否 | 说明应用用途与为何需要这些信息,展示在同意页 |
| Logo URL | 否 | 同意页展示;留空时回退到绑定群的头像 |
回调地址(redirect_uri) | 是 | 授权完成后平台将用户送回的地址,必须与代码中使用的值完全一致,且为 https:// |
| 权限范围(scope) | 否 | 留空默认 openid profile email;按需追加 group、member_groups,含义见 Scope 与权限 |
| client_credentials | 否 | 应用服务器需要直接调用应用成员组管理 API 时开启,开启后自动获得 members:read 与 member_groups:write;只能维护已有成员的应用成员组,不能自动拉人入群 |
注意:平台 UI 的「应用成员组」页签属于教学群组管理权限;应用服务器使用本应用的管理 API 不要求群创建者本人是教师。
提交后立即生效,应用状态为已激活。创建后可在此页签编辑应用信息、重置凭据或移除应用。
第二步:保存凭据#
应用创建响应中包含两个凭据:
- client_id:应用的公开标识。
- client_secret:仅在创建响应中返回一次。请立即保存到服务器环境变量中。若丢失,可在应用列表中点"重置 Secret"自助重发,旧值立即失效,需同步更新到应用服务器。平台只存 secret 的哈希,任何人都无法查询明文。
第三步:实现授权码 + PKCE#
身份与授权服务唯一支持的用户登录流程是 OAuth 2.0 授权码 + PKCE,符合 OIDC 规范。下面用 curl 说明每一步;实际实现推荐使用所用语言的 OAuth/OIDC 客户端库(如 Python requests-oauthlib、Node openid-client),标准库均支持。
1. 生成 PKCE 参数#
通过本平台创建的应用必须使用 PKCE。生成一对 code_verifier 与 code_challenge:
# code_verifier:43–128 位随机串,仅含 A-Za-z0-9-._~
VERIFIER="mH7vXk2pQ9sL4nR8tW1yZ3aB6cD0eF5gJ8iK2lM4nO6pQ"
# code_challenge = base64url( SHA256( code_verifier ) ),去掉末尾的 =
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -binary -sha256 | openssl base64 -A | tr '+/' '-_' | tr -d '=')
2. 发起授权#
将浏览器跳转到授权端点:
open "https://mooc.pneumas.org/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=code&scope=openid%20profile%20email%20group%20member_groups&state=STATE_RANDOM&code_challenge=$CHALLENGE&code_challenge_method=S256"
用户在同意页确认后,浏览器跳回回调地址:
https://app.example.com/callback?code=ONE_TIME_CODE&state=STATE_RANDOM
应用必须校验 state 与发起时一致,然后立即用 code 换 token。
3. 换 token#
curl -X POST https://mooc.pneumas.org/oauth/token \
-u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \
-d grant_type=authorization_code \
-d code=ONE_TIME_CODE \
-d redirect_uri=https://app.example.com/callback \
-d code_verifier=$VERIFIER
响应:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email group member_groups",
"refresh_token": "…",
"id_token": "eyJ…"
}
4. 获取用户信息#
curl https://mooc.pneumas.org/oauth/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"
响应按 token 的 scope 过滤字段。带 group 与 member_groups 时的完整形状:
{
"sub": "10042",
"name": "用户 10042",
"preferred_username": "user_10042",
"email": "user10042@example.com",
"email_verified": true,
"group": {
"id": 7,
"slug": "demo-group",
"name": "示例群组",
"is_member": true,
"role": "member"
},
"member_groups": [
{ "id": 11, "name": "默认成员组", "is_default": true, "sort": 0 },
{ "id": 12, "name": "高级会员", "is_default": false, "sort": 1 }
]
}
至此应用已获得"用户 10042 是示例群组的高级会员"这一事实,后续如何使用由应用业务决定。
上线前检查#
- 持久化并定时刷新 refresh_token:access token 1 小时过期,刷新时平台会重新校验群成员关系。
- 校验 id_token 签名(推荐):用
/.well-known/openid-configuration中的jwks_uri取公钥验签,并校验iss、aud、exp。 - 设计非成员体验:当 userinfo 返回
is_member=false时应用展示什么,应在上线前确定。
常见错误#
| 现象 | 原因 | 处理 |
|---|---|---|
| 同意页报"客户端不存在或未激活" | client_id 错误,或应用已被禁用 | 核对 client_id;被禁用需联系平台管理员恢复 |
| 同意页报"新客户端必须使用 PKCE" | 请求未带 code_challenge | 应用必须使用 PKCE,生成参数并携带 code_challenge 与 code_challenge_method=S256 |
| 同意页报"回调地址不在注册列表中" | redirect_uri 与注册值不一致(含大小写、末尾斜杠) | 逐字符核对,必要时在应用编辑中修改注册值 |
token 返回 invalid_grant | code 已使用、已过期,或 redirect_uri 与授权时不一致 | code 单次有效、10 分钟过期;重新发起授权 |
token 返回 invalid_client | client_id / client_secret 错误,或应用被禁用 | 核对凭据;被禁用需联系管理员 |
| userinfo 返回 401 | access token 过期或已吊销 | 用 refresh_token 刷新 |
