---
title: Layer 3 — 返回规则
description: 配置认证结果如何回传给应用 —— 浏览器回调、原生 status polling、一次性 REVEAL、原生 direct-issue，或 OIDC。
editUrl: true
head: []
template: doc
sidebar:
  order: 4
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { CardGrid, LinkButton, LinkCard } from "@astrojs/starlight/components";

:::tip[三层规则模型的一部分]
返回规则（Layer 3）是三层规则之一。概述介绍了允许列表、默认拒绝、评估顺序，以及三层如何组合。

<LinkButton href="/zh-cn/application-rules/overview/" variant="secondary" icon="left-arrow">阅读概述</LinkButton>
:::

返回规则控制**认证结果如何交付给应用**。系统会检查两次：第一次在 `/establish` 时，检查请求声明的每一种返回方式；第二次在实际执行所选方式时，例如调用 `/status-poll` 或通过 OIDC `/token` 兑换。

## 支持的 return method

| 方式 | `payload`（由认证请求提供） | 用途 |
|---|---|---|
| `CALLBACK` | `{ "callbackUrl": "https://..." }` | Web 应用 —— 认证完成后浏览器被重定向到应用的某个 URL。 |
| `STATUS_POLL` | `{}` | 原生客户端轮询 `connect POST /status-poll`，以获知认证请求何时变为 realized。 |
| `REVEAL` | `{}` | 面向开发者、CLI 或手动集成。登录完成后，`via.sudomimus.com` 会一次性展示访问令牌和/或刷新令牌，由用户手动复制；另一端没有接收结果的应用。 |
| `DIRECT_ISSUE` | `{}` | 为应用启用 `native-api` 的一次性签发端点，包括 Steam ticket（`POST /direct-issue/steam-ticket`）和 AccessKey（`POST /direct-issue/access-key`）。令牌在同一次请求中签发，不经过 `/establish` 或 `/redeem`。 |
| `OIDC` | *（不在认证请求中声明）* | 将应用启用为 OIDC 接入方，通过 `oidc.sudomimus.com` 使用标准 `authorization_code` + PKCE 流程。 |

## CALLBACK —— 应用规则形状

应用上的 `CALLBACK` 规则携带的是**允许的回调主机名**，**不是**完整 URL。真实的 URL 在每次 `/establish` 时由应用提供；规则决定的是这个 URL 的 hostname 是否被允许。

```json
{
  "returnMethod": "CALLBACK",
  "payload": {
    "allowedCallbackDomains": ["client.example.com", "admin.example.com"]
  },
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

Hostname 比较是**精确、不区分大小写**的 —— `client.example.com` **不会**隐式包含 `sub.client.example.com`。两者都要允许就要分别列出来。

## STATUS_POLL —— 应用规则形状

`STATUS_POLL` 规则的 `payload` 为空；规则存在即表示启用该返回方式。

```json
{
  "returnMethod": "STATUS_POLL",
  "payload": {},
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

## REVEAL —— 应用规则形状

`REVEAL` 规则的 `payload` 控制**登录成功后向用户展示哪些令牌**。`includeAccessToken` 和 `includeRefreshToken` 至少有一个必须为 `true`。

```json
{
  "returnMethod": "REVEAL",
  "payload": {
    "includeAccessToken": true,
    "includeRefreshToken": true
  },
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

认证请求声明 `REVEAL` 后，Sudomimus 会在准入阶段直接签发令牌并随响应返回，同时将认证请求标记为 redeemed。此后再对同一次登录调用 `/redeem`，会返回 `InquiryAlreadyRedeemed`。多条匹配的 `REVEAL` 规则按 **OR** 合并：只要任一规则允许访问令牌，结果就包含访问令牌；刷新令牌同理。

:::caution[一次性展示，无法找回]
通过 REVEAL 展示的令牌不会被持久保存到任何用户可见的地方；它们只在登录后的页面上展示一次，之后再也不会重发。用户必须在离开页面之前自己复制下来。REVEAL 适用于开发者工具、CLI 集成或一次性手动绑定 —— **不适合**生产环境下的终端用户认证。
:::

### 与其他 return method 共存

REVEAL 在 `via.sudomimus.com` 页面上有最高优先级：它会**抑制** CALLBACK 的自动跳转，让用户先复制令牌。如果同时声明了 CALLBACK，用户随后会看到一个 "Continue to app" 按钮可以手动跳过去。STATUS_POLL 仍然会按时告诉轮询的客户端 `realized`，但客户端再调 `/redeem` 时会失败 —— 因为 REVEAL 已经 redeem 过 inquiry 了。

## DIRECT_ISSUE —— 应用规则形状

`DIRECT_ISSUE` 规则的 `payload` 同样为空。为应用添加该规则，即可启用 `native-api` 的 Steam ticket 和 AccessKey 一次性签发端点。

```json
{
  "returnMethod": "DIRECT_ISSUE",
  "payload": {},
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

`DIRECT_ISSUE` **不**出现在 `/establish` 的 `returnMethods` 声明里 —— 那些端点根本不经过 `/establish`。Layer 3 校验仍然会在 `native-api` 的 handler 内部运行时进行。

## OIDC —— 应用规则形状

`OIDC` 规则把应用启用为 OpenID Connect 接入方，可通过 `oidc.sudomimus.com` 接入。Payload 携带标准的 OIDC 客户端配置：

```json
{
  "returnMethod": "OIDC",
  "payload": {
    "redirectUris": ["https://app.example.com/oidc/callback"],
    "postLogoutRedirectUris": ["https://app.example.com/"],
    "allowedScopes": ["openid", "email", "profile", "offline_access"],
    "tokenEndpointAuthMethod": "private_key_jwt"
  },
  "accessTokenTtlSeconds": null,
  "refreshTokenTtlSeconds": null
}
```

- **`redirectUris`** —— 完整 URI **精确匹配**。RP 在 `/authorize` 中传入的 `redirect_uri` 必须与列表中某一项**逐字节**相等。不支持前缀或通配。
- **`postLogoutRedirectUris`** —— 完整 URI 精确匹配，用于 `/end-session`。
- **`allowedScopes`** —— 客户端允许请求的 OIDC scope 集合。`openid` 必须存在；支持 `email`、`profile`、`offline_access`。请求列表外的 scope 会在 `/authorize` 阶段失败。
- **`tokenEndpointAuthMethod`** —— 取以下之一：`"private_key_jwt"`（机密客户端，使用 JWT assertion 在 `/token` 上做客户端认证）、`"client_secret_basic"`（机密客户端，在 HTTP `Authorization: Basic` 头中携带共享密钥）、`"client_secret_post"`（机密客户端，在 `/token` 的表单体中发送共享密钥），或 `"none"`（公开客户端，**必须使用 PKCE**）。

OIDC 规则不会出现在 `/establish` 的认证请求中，因为 OIDC 流程使用自己的 `/authorize` 端点。

完整流程与库集成示例见 [OIDC 接入方](/zh-cn/oidc/flow/)。

## 在 `/establish` 上声明

`/establish` 中的 `returnMethods` 同时承担两个作用：声明本次认证请求使用的返回方式，并携带具体的 `callbackUrl`，因为应用规则无法预先知道该值。

```json
{
  "applicationAnchor": "my-app",
  "returnMethods": [
    {
      "type": "CALLBACK",
      "payload": { "callbackUrl": "https://client.example.com/auth/return" }
    },
    {
      "type": "STATUS_POLL",
      "payload": {}
    },
    {
      "type": "REVEAL",
      "payload": {}
    }
  ]
}
```

`DIRECT_ISSUE` 与 `OIDC` 不会在 `/establish` 上声明 —— 这两种流程都不经过它。

- 字段缺失 → 返回规则不收紧；运行时仍然按应用规则检查。
- 字段存在且为空数组 → 拒绝。
- 字段存在且非空 → 在 `/establish` 阶段每一项都会和应用返回规则校验。`CALLBACK` 时，URL 的 hostname 必须匹配某条 `CALLBACK` 规则的 `allowedCallbackDomains`；`STATUS_POLL` 和 `REVEAL` 时，应用必须存在一条同种方法的规则。

## 示例

应用配置了一条返回规则：`CALLBACK`，`allowedCallbackDomains: ["client.example.com"]`。

| Inquiry 的 `callbackUrl` | 结果 |
|---|---|
| `https://client.example.com/return` | 通过 |
| `https://Client.Example.Com/return` | 通过（不区分大小写） |
| `https://sub.client.example.com/return` | 拒绝（不隐式包含子域名） |
| `https://attacker.com/?redirect=client.example.com` | 拒绝（hostname 是 `attacker.com`） |

`STATUS_POLL` 在 `/status-poll` 运行时会再次校验：应用是否仍有 `STATUS_POLL` 规则，并且 inquiry 的收紧（如果有）是否仍然放行它。

## 为什么只比 hostname（针对 CALLBACK）

Sudomimus 只比较 hostname，不检查 path 或查询参数。平台不了解应用自身的 URL 结构，要求维护精确路径允许列表会增加不必要的负担。应用必须在自己的路由层保证回调处理安全；Sudomimus 负责限制浏览器可以返回的**来源（origin）**。

（OIDC 的 `redirectUris` 走的是另一套模型 —— OIDC 标准要求**完整 URI 精确匹配**，且大多数 OIDC 库本身就为每个客户端固定一个 redirect URI。）

## 相关链接

<CardGrid>
<LinkCard
    title="三层规则模型"
    description="整体模型：允许列表、默认拒绝、评估顺序，以及三层如何组合。"
    href="/zh-cn/application-rules/overview/"
/>
<LinkCard
    title="Layer 1 —— 认证规则"
    description="向用户提供哪些认证方式。"
    href="/zh-cn/application-rules/authentication-rules/"
/>
<LinkCard
    title="Layer 2 —— 身份准入规则"
    description="认证后的身份准入检查，可按邮箱等身份信息建立允许列表。"
    href="/zh-cn/application-rules/realize-rules/"
/>
<LinkCard
    title="OIDC 接入方"
    description="端到端的 OIDC 流程，会用到 OIDC return rule。"
    href="/zh-cn/oidc/flow/"
/>
</CardGrid>