锐淏统一认证RAYHAW SSO 接入文档
RAYHAW SSO · TECHNICAL DOCUMENT

锐淏统一认证服务
接入技术文档

OIDC / OAuth 2.1 单点登录 · 用户资料 API · 邮箱提醒 SDK

文档版本
v1.0.0
发布日期
2026 年 10 月 18 日
服务地址
https://sso.rayhaw.com
适用对象
接入系统开发者、运维与安全评审人员
吉林省锐淏科技有限公司 400-986-5862 · contact@rayhaw.cn · https://rayhaw.com/ 吉ICP备2026003747号-2 · 吉公网安备22070202000253号
DEVELOPER DOCS · OIDC / OAUTH 2.1

接入锐淏统一认证

用标准 OIDC 协议接入锐淏 SSO:用户使用手机号登录一次,即可在你的业务系统中获得可信身份。资料由 SSO 统一维护,修改后全平台同步。

Authorization CodePKCE S256RS256OIDC 1.0

接入流程

01创建应用

在管理后台获取 client_id、client_secret,登记回调地址与 scope。

02跳转授权

生成 state、nonce、PKCE,跳转 SSO 登录。

03服务端换令牌

用授权码换取 ID Token / Access Token / Refresh Token。

04建立本地会话

校验 ID Token,调用 UserInfo,建立业务系统自己的登录态。

重要:密码只在锐淏 SSO 输入。业务系统不得收集用户密码,也不得把 client_secret 放入前端代码。

1. 创建应用

联系锐淏科技开通接入权限后,登录 管理后台 → 应用管理 → 创建应用。需要准备:

配置示例说明
应用名称锐淏云工作台显示在授权页
client_idrayhaw-workbench3–64 位小写字母、数字、点、下划线、短横线
回调地址https://app.example.com/auth/rayhaw/callback精确匹配,生产必须 HTTPS
登出回调https://app.example.com/可选
授权范围openid profile email phone offline_access按业务最小化申请

创建成功后 client_secret 只显示一次,请保存到服务端环境变量。

.env
RAYHAW_SSO_BASE=https://sso.rayhaw.com
RAYHAW_CLIENT_ID=rayhaw-workbench
RAYHAW_CLIENT_SECRET=创建时显示的密钥
RAYHAW_REDIRECT_URI=https://app.example.com/auth/rayhaw/callback

2. 发起登录(PHP 示例)

生成一次性 state、nonce 和 PKCE code_verifier,保存到 Session 后跳转:

login.php
<?php
session_start();
$base = 'https://sso.rayhaw.com';
$state = bin2hex(random_bytes(24));
$nonce = bin2hex(random_bytes(24));
$verifier = rtrim(strtr(base64_encode(random_bytes(48)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');

$_SESSION['rayhaw_state'] = $state;
$_SESSION['rayhaw_nonce'] = $nonce;
$_SESSION['rayhaw_verifier'] = $verifier;

$params = [
  'response_type' => 'code',
  'client_id' => 'rayhaw-workbench',
  'redirect_uri' => 'https://app.example.com/auth/rayhaw/callback',
  'scope' => 'openid profile email phone offline_access',
  'state' => $state,
  'nonce' => $nonce,
  'code_challenge' => $challenge,
  'code_challenge_method' => 'S256',
];
header('Location: ' . $base . '/oauth/authorize?' . http_build_query($params));
exit;

回调时先校验 state,再用授权码在服务端换取令牌:

callback.php
if (!hash_equals($_SESSION['rayhaw_state'] ?? '', $_GET['state'] ?? '')) {
    exit('state 校验失败');
}

$ch = curl_init('https://sso.rayhaw.com/oauth/token');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => http_build_query([
    'grant_type' => 'authorization_code',
    'code' => $_GET['code'],
    'redirect_uri' => 'https://app.example.com/auth/rayhaw/callback',
    'code_verifier' => $_SESSION['rayhaw_verifier'],
  ]),
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_USERPWD => 'rayhaw-workbench:' . getenv('RAYHAW_CLIENT_SECRET'),
  CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
]);
$tokens = json_decode(curl_exec($ch), true);
必须校验 ID Token:从 /oauth/jwks 取公钥,验证 RS256 签名,并校验 iss、aud、exp、nonce。不要只在浏览器里解析 JWT。

OIDC 端点

端点地址方法
Discovery/.well-known/openid-configurationGET
JWKS/oauth/jwksGET
Authorization/oauth/authorizeGET
Token/oauth/tokenPOST form
UserInfo/oauth/userinfoGET / POST Bearer
End Session/oauth/logoutGET / POST
Revocation/oauth/revokePOST
Introspection/oauth/introspectPOST 机密客户端

Issuer 固定为 https://sso.rayhaw.com。生产环境不允许使用其他域名签发令牌。

授权请求参数

参数必填说明
response_type是固定 code
client_id是后台创建的应用标识
redirect_uri是与后台登记值逐字符一致
scope是必须包含 openid
state强烈建议随机值,回调校验,防 CSRF
nonce强烈建议写入 ID Token,防重放
code_challenge公共客户端必填PKCE S256 挑战值
code_challenge_method使用 PKCE 时必填仅 S256
prompt否none / login / consent
max_age否最近认证最大秒数

授权码 60 秒有效、一次性使用,并绑定 client、回调地址、PKCE、用户与会话。

换取令牌

HTTP
POST /oauth/token HTTP/1.1
Host: sso.rayhaw.com
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=...
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Frayhaw%2Fcallback
&code_verifier=...
200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "scope": "openid profile email phone offline_access",
  "refresh_token": "..."
}

Access Token 1 小时;Refresh Token 30 天且每次刷新轮换。仅在 scope 含 offline_access 时返回刷新令牌。

UserInfo

请求
GET /oauth/userinfo
Authorization: Bearer <access_token>
响应
{
  "sub": "e21895b7bf7cf8bc2c7b769f753c15454",
  "nickname": "张三",
  "picture": "https://sso.rayhaw.com/uploads/avatars/u1-xxxx.jpg",
  "gender": "male",
  "birthdate": "1990-01-01",
  "email": "zhangsan@example.com",
  "email_verified": true,
  "email_bound": true,
  "email_prompt_dismissed": false,
  "phone_number": "+8613800138000",
  "phone_number_verified": true
}
scope返回字段
openidsub
profile昵称、头像、性别、生日、资料更新时间、邮箱提醒状态
email邮箱与验证状态
phone手机号与验证状态

sub 是稳定的用户标识,请作为业务系统主键,不要使用手机号或邮箱。

刷新令牌与登出

刷新

HTTP
POST /oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=...

每次刷新都会返回新的 Refresh Token,旧令牌立即失效。若返回 invalid_grant,清除本地登录并让用户重新登录。

登出

跳转
https://sso.rayhaw.com/oauth/logout
  ?client_id=rayhaw-workbench
  &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2F
  &state=...

登出回调必须已在后台登记。SSO 会清除统一会话并撤销当前会话的 Refresh Token。

用户资料 API

SSO 是资料唯一数据源。头像、昵称、邮箱、性别、出生日期、身份证号在任意接入应用修改后,全部平台同步。

读取
GET /api/v1/me
Authorization: Bearer <access_token>
修改
PATCH /api/v1/me
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "nickname": "新的昵称",
  "gender": "female",
  "birth_date": "1992-08-18"
}
scope能力
profile:write修改昵称、头像、性别、生日、身份证、密码;关闭邮箱提醒
email:write发送/确认邮箱验证码
高敏感权限:只有业务确有必要时才申请 profile:write / email:write,并在用户授权页明确说明。不要在前端保存 client_secret。

邮箱绑定

邮箱需完成验证码验证,流程为:

  1. POST /api/v1/me/email/send-code,body {"email":"new@example.com"};
  2. 用户查收 6 位验证码,15 分钟内有效;
  3. POST /api/v1/me/email/confirm,body {"email":"new@example.com","code":"123456"}。

验证成功前原邮箱继续有效。绑定状态会通过 UserInfo 的 email、email_verified 返回。

头像上传

JSON
POST /api/v1/me/avatar
Authorization: Bearer <access_token>
Content-Type: application/json

{"avatar":"data:image/jpeg;base64,/9j/4AAQSk..."}

支持 JPG / PNG / WebP,≤ 2 MB;服务端重新编码为 512×512 JPEG,拒绝 SVG、伪造 MIME 与损坏图片。

邮箱绑定提醒 SDK

未绑定邮箱时,在任意接入站点显示提醒。用户点击“不再提醒”后,状态全局生效。

HTML
<link rel="stylesheet" href="https://sso.rayhaw.com/sdk/rayhaw-sso.css">
<script src="https://sso.rayhaw.com/sdk/rayhaw-sso.js" defer></script>

<script>
window.addEventListener('DOMContentLoaded', function () {
  RayhawSSO.init({
    ssoBaseUrl: 'https://sso.rayhaw.com',
    clientId: 'rayhaw-workbench',
    user: {
      email_bound: false,
      email_prompt_dismissed: false
    },
    bindEmailUrl: 'https://sso.rayhaw.com/account?tab=email&bind_email=1',
    dismissEndpoint: '/api/sso/dismiss-email-reminder'
  });
});
</script>

dismissEndpoint 是接入系统自己的同源代理接口,由服务端携带 Access Token 调 SSO API。不要把长期令牌暴露给浏览器。

错误码

错误码场景处理
invalid_request参数缺失、redirect_uri 不匹配、PKCE 不合法检查参数与后台配置
invalid_clientclient_id/secret 错误、应用停用更新密钥或启用应用
invalid_grant授权码/刷新令牌无效、过期、已用、PKCE 失败重新发起登录,不自动重试
invalid_scopescope 未授权后台勾选 scope,重新授权
login_requiredprompt=none 且未登录改为正常跳转登录
consent_requiredprompt=none 且未授权应用显示授权页或去掉 prompt=none
access_denied用户拒绝授权提示用户,可稍后重试
invalid_tokenAccess Token 无效/过期/会话已退出重新登录
server_error服务端异常稍后重试并联系技术支持

部署上线

站点根目录:必须指向项目的 public/ 目录,禁止把项目根目录直接暴露给 Web。
安装
cd /www/wwwroot/sso.rayhaw.com
php bin/install.php \
  --url=https://sso.rayhaw.com \
  --db-host=127.0.0.1 --db-port=3306 \
  --db-name=rayhaw_sso --db-user=rayhaw_sso --db-pass='数据库密码' \
  --admin-phone=13800138000 --admin-password='强密码' \
  --admin-name='系统管理员' --demo
  • PHP 8.0+,扩展:pdo_mysql、openssl、mbstring、curl、gd;不依赖 fileinfo;
  • MySQL 5.7+,字符集 utf8mb4;
  • nginx 根目录 public/,强制 HTTPS;
  • .env 属主必须是 PHP-FPM 运行用户(宝塔通常为 www),权限 600;只改权限不改属主仍会报 Permission denied;
  • 计划任务每 5 分钟:php bin/cron.php;
  • 后台配置阿里云短信与 SMTP,并发送测试。

完整步骤见项目 docs/05-部署运维手册.md。

安全要求

state + nonce

每次随机,回调严格校验,防 CSRF 与重放。

PKCE S256

公共客户端强制;机密客户端也建议启用。

服务端换令牌

client_secret 与令牌不进入浏览器。

精确回调

禁止通配符;生产环境必须 HTTPS。

验证 ID Token

验签 + iss/aud/exp/nonce,不能只解析。

一次性使用

授权码 60 秒;Refresh Token 轮换。

常见问题

回调地址明明一样,为什么还是不一致?

逐字符检查协议、端口、末尾斜杠、大小写与 URL 编码。后台采用精确匹配,不支持通配符。

登录后一直循环跳转?

检查回调路由是否指向登录页、Session 是否保存、HTTPS 下 Cookie 是否 Secure、反向代理是否透传 Host/Proto。

UserInfo 没有昵称或邮箱?

Access Token 缺少 profile / email scope;在后台勾选后让用户重新授权。

为什么在业务系统改了昵称,SSO 也变了?

这是设计行为:SSO 是唯一资料源,业务系统通过资料写入 API 修改,全部平台同步。

邮箱提醒如何做到全平台不再弹?

用户点击“不再提醒”后写入 SSO 用户资料;所有接入应用读取 UserInfo 的 email_prompt_dismissed 后不再显示。

支持 CAS 或 SAML 吗?

v1.0.0 仅支持 OIDC / OAuth 2.1。老系统可增加 OIDC 适配层,或等待后续版本。

上线检查清单