思弈云 OAuth
登录

平台简介

思弈云 OAuth 开放平台是一套基于思弈云账号体系的第三方授权接入系统。平台仅设一个超级账户(即思弈云账号),由该账户在管理后台创建第三方应用,并为每个应用签发 App ID(Client ID)App Secret(Client Secret)。第三方应用通过标准 OAuth2 授权码模式,引导用户到平台的独立授权页完成授权,从而获得访问用户信息的令牌。

本实现采用 OAuth 2.0 授权码模式(Authorization Code Grant),并叠加 state 防 CSRF、redirect_uri 强校验、令牌单向哈希存储等安全措施。

GET https://edu.dddd.ink/oauth/authorize.php
POST https://edu.dddd.ink/oauth/token.php
GET https://edu.dddd.ink/oauth/userinfo.php

快速接入

从创建应用到完成首个登录,共 4 步:

Step 1 · 创建应用

使用平台唯一的超级账户登录管理后台 → 点击「创建应用」→ 填写应用名称、图标、回调地址。创建成功后,系统会仅此一次展示 App IDApp Secret,请立即保存。

Step 2 · 引导用户授权

在你的登录按钮中,将用户浏览器重定向到授权页:

# 参数需 URL 编码
https://edu.dddd.ink/oauth/authorize.php?response_type=code
  &client_id=YOUR_APP_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid profile email
  &state=YOUR_RANDOM_STATE

Step 3 · 处理回跳

用户允许后,平台跳转到你的回调地址并携带一次性授权码:

GET https://yourapp.com/callback?code=authcode_XXXX&state=YOUR_RANDOM_STATE

用户在授权页拒绝时携带:

GET https://yourapp.com/callback?error=access_denied&state=YOUR_RANDOM_STATE

Step 4 · 换取令牌并获取用户信息

服务端用 codeaccess_token,再用令牌调 /userinfo 获取用户信息。

应用与凭据

凭据说明保密
App ID(client_id)应用唯一公开标识,形如 siyiyun_xxxxxxxx公开
App Secret(client_secret)应用机密,换取令牌时校验仅服务端
redirect_uri授权完成后回调地址,须与注册完全一致-

App Secret 仅在创建与重置时明文展示一次,丢失后请在管理后台「重置密钥」,旧密钥立即失效。应用可被启用/停用/删除,删除会连带吊销其全部令牌。

授权入口 /oauth/authorize.php

浏览器端点,由第三方应用重定向触发。校验通过后展示独立授权页(含应用名、图标、主机、Scope 说明与当前登录账号),由用户点击「允许 / 拒绝」。

请求参数

参数必填说明
response_type固定 code
client_id应用 App ID
redirect_uri是*与注册回调一致;缺省时使用注册值
scope申请的权限范围,空格分隔,缺省用应用默认
state推荐随机串,回调原样返回,用于 CSRF 防护

平台采用单一默认账户,授权时不需额外登录,校验通过后直接展示授权页。

令牌接口 /oauth/token.php

应用服务端使用,请求体 application/json

① 授权码换取(grant_type=authorization_code)

POST https://edu.dddd.ink/oauth/token.php
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "authcode_XXXX",
  "client_id": "YOUR_APP_ID",
  "client_secret": "YOUR_APP_SECRET",
  "redirect_uri": "https://yourapp.com/callback"
}

成功响应:

HTTP/1.1 200
{
  "access_token": "at_XXXX",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "rt_XXXX",
  "scope": "openid profile email"
}

② 刷新令牌(grant_type=refresh_token)

POST https://edu.dddd.ink/oauth/token.php
{
  "grant_type": "refresh_token",
  "refresh_token": "rt_XXXX",
  "client_id": "YOUR_APP_ID",
  "client_secret": "YOUR_APP_SECRET"
}

刷新采用滚动轮换:每次返回全新的 access_tokenrefresh_token,旧 refresh_token 随即失效。

用户信息 /oauth/userinfo.php

携带访问令牌获取当前授权用户的身份信息。令牌可通过 Authorization: Bearer <token> 头,或查询参数 ?access_token= 传递。

GET https://edu.dddd.ink/oauth/userinfo.php
Authorization: Bearer at_XXXX

响应(字段随 scope 裁剪):

HTTP/1.1 200
{
  "sub": "Ab12Cd34Ef56",
  "user_id": "Ab12Cd34Ef56",
  "username": "张三",
  "email": "user@qq.com",
  "role": 1,
  "scope": "openid profile email"
}

Scope 说明

Scope含义userinfo 返回字段
openid基础身份标识sub, user_id
profile昵称与个人资料username, role
email邮箱地址email
uid思弈云用户 UIDuser_id

错误码表

OAuth 标准错误(/token 与 /userinfo 返回 JSON)

HTTPerror说明
400invalid_request请求参数缺失或不合法
400invalid_grant授权码/刷新令牌无效、过期、已使用或已被吊销
400unsupported_grant_type不支持的 grant_type
401invalid_clientclient_id 或 client_secret 错误,应用被停用
401invalid_tokenaccess_token 缺失、无效、过期
400access_denied用户在授权页拒绝了请求

授权页回跳 error 参数

error说明
invalid_client非法的 client_id
unauthorized_client应用已被停用
unsupported_response_typeresponse_type 必须为 code
invalid_requestredirect_uri 与注册不一致等

代码示例

PHP 服务端换取令牌并获取用户信息

function oauth_exchange($code, $clientId, $clientSecret, $redirectUri) {
  // 1. 换令牌
  $resp = http_post_json(BASE_URL.'/oauth/token.php', [
    'grant_type' => 'authorization_code', 'code' => $code,
    'client_id' => $clientId, 'client_secret' => $clientSecret,
    'redirect_uri' => $redirectUri,
  ]);
  $token = json_decode($resp, true);

  // 2. 取用户信息
  $user = http_get(BASE_URL.'/oauth/userinfo.php', ["Authorization: Bearer {$token['access_token']}"]);
  return json_decode($user, true);
}

JavaScript 前端发起授权

const clientId = 'YOUR_APP_ID';
const state = Math.random().toString(36).slice(2);
sessionStorage.setItem('oauth_state', state);
const url = BASE + '/oauth/authorize.php?response_type=code'
  + '&client_id=' + encodeURIComponent(clientId)
  + '&redirect_uri=' + encodeURIComponent(redirectUri)
  + '&scope=' + encodeURIComponent('openid profile email')
  + '&state=' + state;
location.href = url;

安全建议

  • 校验回调中的 state 必须与发起时一致,否则拒绝(防 CSRF)。
  • client_secret 只能存放于你的服务端,严禁暴露在前端代码。
  • 授权码一次性有效,仅在回调后立即使用,并精确校验 redirect_uri
  • 令牌通过 HTTPS 传输;过期后使用 refresh_token 刷新而非重新登录。
  • 访问令牌存取采用 SHA-256 加盐哈希,即使数据库泄露也无法反推原始令牌。

常见问题

  • 为什么授权后回调里没有 code? 查看回调参数是否含 error=access_denied,通常是用户拒绝了授权。
  • 提示 redirect_uri 与注册不一致? 授权地址中的回调必须与应用注册的完全一致(含路径、端口、协议)。
  • App Secret 丢失了? 在管理后台应用中点击「重置密钥」,新密钥仅展示一次。
  • token 报 invalid_grant 授权码重放? code 仅能用一次,每次授权都会生成全新授权码。