版本:v1.1 | 生效日期:2026-08-18 | 维护方:Beats 彼邑网络 AI 实验室
Beats ID 是 beats.yun 云平台的统一账号体系。任何部署在该平台上的应用(Web 系统、内部工具、SaaS 产品),都可以接入 Beats ID,让用户使用平台账号一键登录,无需二次注册。
| 名词 | 含义 |
|---|---|
| Beats ID / 平台 | beats.yun,账号与授权服务所在方 |
| 应用 / Client | 你开发的、要接入平台登录的系统 |
| 资源所有者 | 最终用户 |
每个应用在接入前,必须先在平台登记,获得一对凭证:
| 凭证 | 说明 |
|---|---|
client_id |
应用标识,形如 beatsapp_xxxxxxxx,可公开 |
client_secret |
应用密钥,仅创建时返回一次,只存于你应用的后端,绝不可出现在前端代码、App 包体或公开仓库中 |
登记方式:联系平台管理员,提供①应用名称 ②回调地址(redirect_uri,可多个)。管理员通过管理接口创建后把凭证交给你。
管理员自助接口(需管理员账号会话):
POST /api/admin/oauth/clients,Body:{"name":"应用名","redirect_uris":["https://你的域名/callback"]}
回调地址规则:授权完成后平台只把用户送回登记过的地址,精确匹配(含协议、域名、端口、路径),不支持通配符。请至少登记一个生产地址;本地开发可额外登记 http://127.0.0.1:端口/callback。
用户浏览器 你的应用 beats.yun 平台
│ ① 点"使用 Beats ID 登录" │ │
│────────────────────────>│ │
│ ② 302 跳转到平台授权页 │ │
│─────────────────────────────────────────────────>│
│ 用户在平台登录并点"同意授权" │
│<─────────────────────────────────────────────────│
│ ③ 带回授权码 code 跳回你的回调地址 │
│────────────────────────>│ │
│ │ ④ 后端用 code 换 token │
│ │───────────────────────>│
│ │<── access_token ───────│
│ │ 用 token 拉取用户信息 │
│ │───────────────────────>│
│<── 登录成功,建立你的本地会话 ──│ │
把「使用 Beats ID 登录」按钮链接到:
GET {平台地址}/oauth/authorize
?client_id=你的client_id
&redirect_uri=你的回调地址(需 URL 编码)
&state=随机防串改字符串
| 参数 | 必填 | 说明 |
|---|---|---|
client_id |
✅ | 登记时获得 |
redirect_uri |
✅ | 必须与登记的回调地址完全一致 |
state |
强烈建议 | 你生成的随机串,第 3 步原样带回,用于防 CSRF 攻击。务必校验 |
用户同意后,浏览器 302 跳转:
{你的redirect_uri}?code=授权码&state=你第1步传的state
state 与第 1 步发出的是否一致,不一致直接拒绝code 有效期 10 分钟、只能使用一次?error=access_denied&state=...必须在你的服务端发起(要用到 client_secret):
POST {平台地址}/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "你的client_id",
"client_secret": "你的client_secret",
"code": "第2步拿到的code",
"redirect_uri": "与第1步完全一致的回调地址"
}
成功响应:
{
"access_token": "R9Brv31Gsg5KoMzl12Td43S20yrVrPN2yjnTUDMrwvE",
"token_type": "Bearer",
"expires_in": 604800
}
expires_in 单位秒,当前为 7 天GET {平台地址}/oauth/userinfo
Authorization: Bearer {access_token}
响应(当前可用字段):
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int | 平台用户唯一 ID,建议作为你系统内的关联主键 |
username |
string | 登录用户名(唯一) |
nickname |
string | 显示昵称 |
phone |
string | 手机号 |
email |
string | 企业邮箱 |
company |
string | 企业名称 |
拿到用户信息后,典型做法:按 id 查你本地用户表 → 不存在则自动建档 → 签发你自己系统的会话(Cookie/JWT)。后续用户与你的系统交互,不再依赖平台。
import os, secrets, requests
from flask import Flask, redirect, request, session, abort
app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET"]
BEATS = "https://beats.yun" # 平台地址
CLIENT_ID = "beatsapp_xxxxxxxx"
CLIENT_SECRET = os.environ["BEATS_CLIENT_SECRET"]
REDIRECT_URI = "http://你的应用地址/callback"
@app.route("/login")
def login():
state = secrets.token_urlsafe(16)
session["oauth_state"] = state
from urllib.parse import quote
return redirect(
f"{BEATS}/oauth/authorize?client_id={CLIENT_ID}"
f"&redirect_uri={quote(REDIRECT_URI, safe='')}&state={state}"
)
@app.route("/callback")
def callback():
if request.args.get("state") != session.pop("oauth_state", None):
abort(403, "state 校验失败")
if request.args.get("error"):
abort(403, "用户拒绝了授权")
token = requests.post(f"{BEATS}/oauth/token", json={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": request.args["code"],
"redirect_uri": REDIRECT_URI,
}, timeout=10).json()
if "access_token" not in token:
abort(502, f"换取令牌失败: {token.get('error')}")
user = requests.get(f"{BEATS}/oauth/userinfo",
headers={"Authorization": f"Bearer {token['access_token']}"},
timeout=10).json()
# TODO: 按 user["id"] 查/建本地用户,然后:
session["uid"] = user["id"]
session["nickname"] = user["nickname"]
return redirect("/")
const express = require("express");
const session = require("express-session");
const crypto = require("crypto");
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
const BEATS = "https://beats.yun"; // 平台地址
const CLIENT_ID = "beatsapp_xxxxxxxx";
const CLIENT_SECRET = process.env.BEATS_CLIENT_SECRET;
const REDIRECT_URI = "http://你的应用地址/callback";
app.get("/login", (req, res) => {
const state = crypto.randomBytes(16).toString("hex");
req.session.oauthState = state;
res.redirect(`${BEATS}/oauth/authorize?client_id=${CLIENT_ID}` +
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&state=${state}`);
});
app.get("/callback", async (req, res) => {
if (req.query.state !== req.session.oauthState) return res.status(403).send("state 校验失败");
if (req.query.error) return res.status(403).send("用户拒绝了授权");
const tokenRes = await fetch(`${BEATS}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: req.query.code,
redirect_uri: REDIRECT_URI,
}),
}).then(r => r.json());
if (!tokenRes.access_token) return res.status(502).send("换取令牌失败: " + tokenRes.error);
const user = await fetch(`${BEATS}/oauth/userinfo`, {
headers: { Authorization: `Bearer ${tokenRes.access_token}` },
}).then(r => r.json());
// TODO: 按 user.id 查/建本地用户
req.session.user = user;
res.redirect("/");
});
| 场景 | 返回 |
|---|---|
| client_id 或 redirect_uri 未登记 | {"error":"无效的 client_id 或 redirect_uri(未注册)"} |
| 用户拒绝授权 | 回调带 error=access_denied |
| 授权码过期 / 已使用 / 不存在 | {"error":"授权码无效或已过期"} |
| client_secret 错误 | {"error":"客户端认证失败"} |
| access_token 无效或过期(userinfo) | HTTP 401 {"error":"令牌无效或已过期"} |
https://beats.yun(http:// 会 301 跳转,但回调地址请直接登记 https:// 版本)id 作为外键存库;手机号、邮箱等资料以平台为准,需要时实时拉取Q:用户在我的应用里能改密码吗? 不能。账号体系归平台统一管理,用户资料修改入口在平台门户。你的应用只做身份消费。
Q:用户在我的应用里"注册"过吗?
不需要。首次 Beats ID 登录时按 id 自动建档即可,对用户无感。
Q:同一用户在多个应用里是同一个人吗?
是。id 全平台唯一且恒定,可用于跨应用识别同一用户(注意遵守隐私合规要求)。
Q:怎么注销我应用的 client?
联系管理员,或管理员调用 DELETE /api/admin/oauth/clients/{client_id}。注销后该应用所有已签发令牌立即失效。
Q:以后会有 refresh token / 更多用户信息字段吗? 会在 v2 规划。接入时把 userinfo 解析写成容错方式(未知字段忽略),即可平滑升级。
本规范随平台 v1 上线发布,后续版本变更将在此文档追加 changelog。
Changelog
http:// 切换为 https://beats.yun;新接入应用的回调地址请直接登记 https://