身份与授权 · 接入指南

接入指南

本文描述从零接入身份与授权服务的完整步骤。接入免审核,应用创建即生效。协议细节见授权码流程,端点完整定义见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 是示例群组的高级会员"这一事实,后续如何使用由应用业务决定。

上线前检查#

  1. 持久化并定时刷新 refresh_token:access token 1 小时过期,刷新时平台会重新校验群成员关系。
  2. 校验 id_token 签名(推荐):用 /.well-known/openid-configuration 中的 jwks_uri 取公钥验签,并校验 iss、aud、exp。
  3. 设计非成员体验:当 userinfo 返回 is_member=false 时应用展示什么,应在上线前确定。

常见错误#

现象原因处理
同意页报"客户端不存在或未激活"client_id 错误,或应用已被禁用核对 client_id;被禁用需联系平台管理员恢复
同意页报"新客户端必须使用 PKCE"请求未带 code_challenge应用必须使用 PKCE,生成参数并携带 code_challenge 与 code_challenge_method=S256
同意页报"回调地址不在注册列表中"redirect_uri 与注册值不一致(含大小写、末尾斜杠)逐字符核对,必要时在应用编辑中修改注册值
token 返回 invalid_grantcode 已使用、已过期,或 redirect_uri 与授权时不一致code 单次有效、10 分钟过期;重新发起授权
token 返回 invalid_clientclient_id / client_secret 错误,或应用被禁用核对凭据;被禁用需联系管理员
userinfo 返回 401access token 过期或已吊销用 refresh_token 刷新