SSO 单点登录

子产品接入 SaaS 平台 SSO,用户在平台登录后可自动获取子产品会话,无需重新注册。

接入流程

  1. 子产品前端检测用户未登录时,重定向到 https://platform.example.com/auth/sso?appSlug=YOUR_SLUG&redirect=https://app.example.com/sso/callback
  2. 用户在平台完成登录后,平台将 SSO Code 传回子产品的 redirect URL(作为 Query 参数 ?code=xxx
  3. 子产品服务端用 SSO Code 换取用户信息:
POST /api/v1/auth/sso/user-info
X-Api-Key: {api_key}
Content-Type: application/json

{ "code": "SSO_CODE_FROM_QUERY" }

// 响应
{
  "userId": "...",
  "email": "user@example.com",
  "tenantSlug": "acme-corp",
  "planType": "Professional",
  "subscriptionStatus": "Active"
}
  • 子产品根据返回的 tenantSlug 设置 PostgreSQL search_path,建立本地会话。
  • Webhook 事件处理

    平台在订阅状态变更时向已配置的端点发送事件,端点需在 5 秒内返回 2xx 状态码,否则将自动重试(最多 3 次)。

    验签(推荐)

    // Node.js 示例
    const crypto = require('crypto');
    
    function verifySignature(payload, signature, secret) {
      const expected = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');
      return expected === signature;
    }
    
    // Express 路由
    app.post('/webhook/platform', express.raw({ type: 'application/json' }), (req, res) => {
      const sig = req.headers['x-webhook-signature'];
      if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
        return res.status(401).send('Invalid signature');
      }
      const event = JSON.parse(req.body);
      console.log('Received event:', event.eventType);
      res.status(200).send('OK');
    });

    多租户 Schema 初始化

    子产品使用 PostgreSQL 多 Schema 隔离租户数据。当收到 subscription.created 事件后,需为新租户创建并初始化 Schema。

    -- 示例:为 acme-corp 创建租户 Schema
    CREATE SCHEMA IF NOT EXISTS "tenant_acme-corp";
    
    SET search_path TO "tenant_acme-corp", public;
    
    -- 执行子产品的 Schema 初始化脚本
    -- 该脚本可在运营后台「产品详情 → Schema 脚本」中维护

    在运营后台「产品管理 → 产品详情」中维护 Schema 初始化脚本,平台会在新订阅创建时自动调用 SSO 将 Slug 传给子产品。

    应用健康检查

    在「产品详情 → 健康检查 URL」填写子产品健康检查接口,平台每 5 分钟自动探测,结果展示在运营仪表盘。

    // 健康检查接口示例(ASP.NET Core)
    app.MapGet("/health", () => Results.Ok(new { status = "healthy", time = DateTime.UtcNow }));

    要求:GET {healthCheckUrl} 返回 2xx 状态码即视为健康,响应时间超过 5 秒视为超时。