开放平台

OAuth2.0 开放 API
安全 · 开放 · 可互联

Secms 内容管理系统 提供基于 OAuth2.0 标准的开放授权层,支持授权码(含 PKCE)、 刷新令牌轮换与客户端凭证三种模式,轻松对接 Web、移动端与第三方系统。

凭据哈希存储

客户端密钥仅以 SHA-256 哈希落库,明文只在创建时展示一次

令牌轮换

刷新令牌每次使用即撤销旧令牌,访问令牌刷新时一并吊销

最小权限

基于 scope 的细粒度授权,缺权限返回 403 insufficient_scope

限速与防重放

令牌端点 60 秒 30 次限速,授权码一次性、10 分钟过期

接入流程

从申请应用到首次调通,共 5 步

1

后台创建客户端

后台「系统设置 → OAuth 客户端」新增应用,填写回调地址、授权方式与权限范围,复制保存 Client ID / Secret

2

引导用户授权

跳转授权端点,用户在确认页同意/拒绝,浏览器回跳携带一次性授权码

3

换取访问令牌

服务端携带授权码与客户端凭证调用令牌端点,换取 access_token 与 refresh_token

4

调用用户 API

请求头携带 Bearer 访问令牌,按 scope 读取用户信息与文章数据

5

续期与撤销

访问令牌过期后用刷新令牌续期(自动轮换),退出登录时调用撤销端点使令牌立即失效

授权端点

引导用户授权,签发一次性授权码。

GET /oauth/authorize
client_id * 客户端 ID(后台创建)
response_type * 固定为 code
redirect_uri * 回调地址,须与白名单完全一致
scope * 空格分隔的权限范围,如 user.basic user.email
state 防 CSRF 随机串,回跳时原样带回
code_challenge PKCE 挑战值(S256 为 Base64URL(SHA256(verifier)))
code_challenge_method plain 或 S256
POST /oauth/authorize
用户点击「同意授权」后提交,携带 client_id / redirect_uri / scope / state / code_challenge 等原参数
decision approve(同意)或 deny(拒绝)
同意后 302 回跳:redirect_uri?code=一次性授权码&state=xxx
拒绝后回跳:redirect_uri?error=access_denied

令牌端点

POST /oauth/token,支持三种授权模式;客户端凭证可经 HTTP Basic 或请求体携带。

授权码模式(推荐)

grant_type=authorization_code code=一次性授权码 redirect_uri=回调地址(须与授权时一致) client_id=xxx client_secret=xxx code_verifier=PKCE 验证值(使用了 PKCE 时必填)

刷新令牌(轮换)

grant_type=refresh_token refresh_token=刷新令牌 client_id=xxx client_secret=xxx
每次使用旧刷新令牌立即失效并签发新对;同时撤销该客户端/用户名下旧访问令牌。有效期 30 天。

客户端凭证(服务端到服务端)

grant_type=client_credentials scope=user.basic client_id=xxx client_secret=xxx
无用户上下文,仅返回访问令牌(token_type 为 Bearer,默认 2 小时有效)。

用户 API(Bearer 保护)

请求头携带 Authorization: Bearer <access_token>,按令牌 scope 返回数据。

GET /api/user/info
{ "code": 0, "msg": "ok", "data": { "id": 2, "username": "xxx", "nickname": "昵称", "avatar": "", "points": 100, "level_id": 0, "group_id": 0, "created_at": "2026-10-23 10:00:00", "last_login_time": "", "email": "o****@test.local" } }
user.basic 返回基础资料;邮箱仅在含 user.email scope 时返回(自动脱敏)。
GET /api/user/articles?page=1&limit=10
{ "code": 0, "msg": "ok", "data": { "list": [{"id": 1, "title": "标题", "cid": 2, "hits": 88, "created_at": "..."}], "page": 1, "limit": 10, "total": 0, "total_page": 0 } }
需 user.articles scope;缺 scope 返回 403 insufficient_scope;仅返回已发布(status=2)未删除文章。
POST /api/oauth/revoke
Authorization: Bearer <access_token>(无需请求体) {"code": 0, "msg": "ok", "data": {"revoked": true}}
撤销当前访问令牌及其关联刷新令牌,调用后立即失效。

权限范围(Scope)

Scope 权限说明 适用端点
user.basic 读取基本资料(用户名、昵称、头像、积分、等级、注册时间) /api/user/info
user.email 读取脱敏邮箱地址 /api/user/info
user.articles 读取已发布文章列表 /api/user/articles

错误码(RFC 6749 标准)

HTTP error 说明
400invalid_request请求参数缺失或非法(如回调地址不在白名单)
400invalid_scope请求的 scope 超出客户端授权范围
400invalid_grant授权码/刷新令牌无效、过期、被使用或不属于当前客户端
400unsupported_grant_type不支持的 grant_type
400unauthorized_client客户端未授权使用该授权方式
401invalid_client客户端凭证无效、密钥错误或客户端已停用
401invalid_token访问令牌无效、过期或已撤销
403insufficient_scope令牌缺少访问该端点所需的 scope
429rate_limited令牌端点请求过于频繁(60 秒 30 次/IP)

错误响应格式:{"error": "...", "error_description": "..."};Bearer API 错误返回 {"code": HTTP码, "msg": "..."}。

代码示例

三种主流语言的接入示例(授权码 + PKCE 全流程)

curl
# 1. 引导授权(浏览器打开) open "https://site.com/oauth/authorize?client_id=xxx&response_type=code&redirect_uri=https%3A%2F%2Fapp.com%2Fcb&scope=user.basic+user.email&state=xyz" # 2. 回调拿到 code 后换取令牌 curl -X POST https://site.com/oauth/token \ -d grant_type=authorization_code \ -d code=授权码 \ -d redirect_uri=https://app.com/cb \ -d client_id=xxx \ -d client_secret=yyy # 3. 调用用户 API curl https://site.com/api/user/info \ -H "Authorization: Bearer 访问令牌"
JavaScript(PKCE)
// 生成 PKCE 挑战 const v = crypto.randomUUID() + crypto.randomUUID(); const verifier = btoa(v).replace(/[^a-zA-Z0-9]/g, ''); const challenge = btoa(String.fromCharCode(...new Uint8Array( await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))))) .replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); // 跳转授权 location.href = '/oauth/authorize?client_id=xxx&response_type=code' + '&redirect_uri=' + encodeURIComponent(cb) + '&scope=user.basic&state=xyz' + '&code_challenge=' + challenge + '&code_challenge_method=S256'; // 回调页换取令牌 const res = await fetch('/oauth/token', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: cb, client_id: 'xxx', client_secret: 'yyy', code_verifier: verifier, }), }); const {access_token, refresh_token} = await res.json();
PHP
// 换取访问令牌 $resp = file_get_contents('https://site.com/oauth/token', false, stream_context_create(['http' => [ 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded", 'content' => http_build_query([ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $redirectUri, 'client_id' => $clientId, 'client_secret' => $clientSecret, ]), 'ignore_errors' => true, ]])); $data = json_decode($resp, true); $token = $data['access_token'] ?? ''; // 调用用户 API $user = json_decode( file_get_contents('https://site.com/api/user/info', false, stream_context_create(['http' => [ 'header' => "Authorization: Bearer ", ]]), true ));

安全实践建议

  • 客户端密钥只允许存储在后端,严禁出现在前端代码或日志中
  • 授权请求务必携带随机 state 并在回调时校验,防止 CSRF
  • 移动端与纯前端应用优先使用 PKCE(S256),避免明文传输 client_secret
  • 令牌过期即刷新,退出登录调用 revoke 端点主动吊销
  • 密钥疑似泄露时,立即在后台重置密钥(全部已签发令牌同步吊销)

后台管理入口

登录后台,进入「系统设置 → OAuth 客户端」,可进行:

  • 新增/编辑第三方应用(回调地址、授权方式、权限范围)
  • 创建/重置客户端密钥(明文仅展示一次)
  • 启用/停用客户端(停用后所有令牌请求立即被拒)
  • 删除客户端(级联撤销全部授权码与令牌)