---
title: "嵌入式登录 - QiSSO"
description: "对接浏览器直连的密码与 MFA 授权接口，处理 CSRF、事务状态和 OIDC 回调。"
canonical_url: "https://docs.mcent.cn/qisso/embedded-login"
---
# 嵌入式登录 - QiSSO

> 对接浏览器直连的密码与 MFA 授权接口，处理 CSRF、事务状态和 OIDC 回调。

嵌入式登录允许业务页面展示自己的账号、密码与 MFA 表单。浏览器将凭据直接提交到 QiSSO，业务服务端继续通过授权码 + PKCE 回调建立会话。这是 OIDC 授权的交互入口，不是密码授权模式。

## 前置条件

1. 管理员为本环境启用嵌入式能力，配置服务端的嵌入式签名密钥。
2. OIDC 应用启用嵌入式登录、授权码与 PKCE，并登记准确的 Web 来源和完整回调。
3. 回调的 Origin 与发起请求的业务页面 Origin 一致。
4. 浏览器允许 QiSSO 所需的 Cookie 随请求发送。跨站部署需验证实际浏览器的第三方 Cookie 策略；遇到阻止时提供跳转 QiSSO 登录的入口。

业务 BFF 先创建登录事务并保留 `state`、`nonce` 与 verifier，只把公开授权参数交给浏览器。Secret 和 verifier 不进入密码表单，密码、TOTP 与备用码也不经过 BFF。

## 交互状态

<table>
<thead>
  <tr>
    <th>
      当前状态
    </th>
    
    <th>
      用户动作
    </th>
    
    <th>
      下一步
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      未初始化
    </td>
    
    <td>
      BFF 创建事务，浏览器调用 init
    </td>
    
    <td>
      保存返回的短期事务字段
    </td>
  </tr>
  
  <tr>
    <td>
      已初始化
    </td>
    
    <td>
      提交密码
    </td>
    
    <td>
      成功授权或进入 MFA
    </td>
  </tr>
  
  <tr>
    <td>
      需要 MFA
    </td>
    
    <td>
      按 <code>
        methods
      </code>
      
       提交 TOTP 或备用码
    </td>
    
    <td>
      成功授权；错误按类型提示或重启
    </td>
  </tr>
  
  <tr>
    <td>
      已授权
    </td>
    
    <td>
      校验 <code>
        redirect_url
      </code>
      
       并导航
    </td>
    
    <td>
      BFF 校验回调、换码和创建本地会话
    </td>
  </tr>
  
  <tr>
    <td>
      过期、已消费或终止
    </td>
    
    <td>
      重新开始
    </td>
    
    <td>
      生成新的 state、nonce 与 PKCE 事务
    </td>
  </tr>
</tbody>
</table>

每个动作使用自己的 loading 状态；提交时防止同一事务并发请求。取消或重启后丢弃旧请求结果，不能让迟到响应跳转或覆盖新事务。

## 请求约定

四个接口均在 Issuer 的 `/embedded/authorize/` 下：

```text
POST /realms/mcent/embedded/authorize/init
POST /realms/mcent/embedded/authorize/password
POST /realms/mcent/embedded/authorize/mfa/totp
POST /realms/mcent/embedded/authorize/mfa/backup-code
```

所有请求使用 JSON 和 `credentials: 'include'`。浏览器自行携带 `Origin`，不要由业务代理伪造。`init` 后的三个接口还需返回的 CSRF 值：

```http
Content-Type: application/json
X-QiSSO-CSRF: <csrf_token>
```

响应只包含授权流程所需的公开字段，不返回 QiSSO 原始会话 Token、密码、MFA 码或完整用户记录。`login_tx` 与 `interaction` 都不是会话凭据，只在本次流程内使用，不持久化到浏览器存储或日志。

## 1. 初始化授权

请求体字段：

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      必需
    </th>
    
    <th>
      约束
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        client_id
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      已启用的注册应用，最长 128 字符
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        redirect_uri
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      已注册完整回调，最长 2048 字符
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        response_type
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      固定 <code>
        code
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scope
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      空格分隔，含 <code>
        openid
      </code>
      
      ，只使用应用允许的身份 scope
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        state
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      32–512 个允许字符，由 BFF 随机生成
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        nonce
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      32–512 个允许字符，与本次 ID Token 验证绑定
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        code_challenge
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      43–128 个允许字符，来源于本次 verifier
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        code_challenge_method
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      固定 <code>
        S256
      </code>
    </td>
  </tr>
</tbody>
</table>

上述随机参数允许字母、数字和 `._~-`，Base64URL 可用于生成。嵌入式接口严格拒绝未知字段，不支持 `plain` PKCE。身份 scope 当前为 `openid profile email roles offline_access`，服务 scope 不用于此流程。

```js
// publicParameters 由本应用的 BFF 生成并绑定到浏览器事务。
const response = await fetch(`${issuer}/embedded/authorize/init`, {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(publicParameters)
})
```

成功响应示意：

```json
{
  "login_tx": "<opaque_transaction>",
  "csrf_token": "<csrf_value>",
  "interaction": "<signed_interaction>",
  "expires_in": 300,
  "identifier_types": ["email", "username", "phone"]
}
```

事务默认 300 秒，服务端可在 60–600 秒范围配置；客户端依据本次 `expires_in` 判断剩余有效期，不把示例数值写死为协议保证。

## 2. 提交密码

```json
{
  "login_tx": "<init_returned_value>",
  "interaction": "<init_returned_value>",
  "identifier": "operator@example.com",
  "password": "<user_entered_password>",
  "remember_me": true
}
```

`identifier` 支持邮箱、用户名或 E.164 格式手机号，例如 `+8613800000000`。`remember_me` 为布尔值，默认 `true`。不要预先检查账号是否存在，也不要根据认证失败向用户泄露账号存在性。

如果需要 MFA：

```json
{
  "status": "mfa_required",
  "methods": ["totp", "backup_code"],
  "expires_in": 240
}
```

只展示本次 `methods` 提供的方法。`backup_code` 是响应中的方法名，对应 URL 路径为 `backup-code`。

## 3. 提交 MFA

TOTP 请求体：

```json
{
  "login_tx": "<init_returned_value>",
  "interaction": "<init_returned_value>",
  "code": "123456",
  "trust_device": false
}
```

备用码使用相同字段提交到 `/mfa/backup-code`，`code` 改为用户提供的单次备用码。TOTP 必须为 6 位数字，备用码为 8–64 个允许的字母、数字或连字符。`trust_device` 默认为 `false`，仅在用户明确选择时发送 `true`。

MFA 挑战还依赖 QiSSO 签名的临时 Cookie，不能仅凭 `login_tx` 在其他浏览器或无 Cookie 请求中继续。

## 4. 完成回调

密码或 MFA 成功后返回：

```json
{
  "status": "authorized",
  "redirect_url": "https://app.example.com/login/callback?code=...&state=...&iss=...",
  "expires_in": 300
}
```

客户端先解析 URL，核对协议、Origin、路径及已登记回调约束，再执行页面导航。不要用简单的字符串前缀检查域名。BFF 仍执行[完整回调校验与换码](https://docs.mcent.cn/qisso/oidc-login)，不能看到 `authorized` 就直接将浏览器标记为已登录。

## CORS 与错误处理

允许来源的响应返回精确 Origin、`Access-Control-Allow-Credentials: true` 与 `Vary: Origin`。允许头包含 `Content-Type`、`X-QiSSO-CSRF`，并公开 `Retry-After`。不允许的来源可能无法通过浏览器读到错误体，应从请求来源与预检开始排查。

```json
{
  "error": "expired_transaction",
  "error_description": "<public_description>"
}
```

<table>
<thead>
  <tr>
    <th>
      HTTP / error
    </th>
    
    <th>
      处理
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        400 invalid_request
      </code>
      
       / <code>
        invalid_pkce
      </code>
    </td>
    
    <td>
      检查字段、注册配置与 S256 参数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403 invalid_origin
      </code>
      
       / <code>
        invalid_csrf
      </code>
    </td>
    
    <td>
      检查精确 Origin、应用 Web 来源、同一 init 的 CSRF
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        400 invalid_transaction
      </code>
      
       / <code>
        expired_transaction
      </code>
      
       / <code>
        transaction_used
      </code>
      
       / <code>
        invalid_transaction_state
      </code>
    </td>
    
    <td>
      清理旧事务，重新初始化
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401 invalid_credentials
      </code>
    </td>
    
    <td>
      通用认证失败提示，允许按策略重试
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401 invalid_mfa_code
      </code>
    </td>
    
    <td>
      保留 MFA 步骤，提示检查验证码
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403 email_not_verified
      </code>
      
       / <code>
        phone_not_verified
      </code>
      
       / <code>
        account_not_active
      </code>
    </td>
    
    <td>
      提示完成验证或联系管理员，不绕过账号策略
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        429 rate_limited
      </code>
      
       / <code>
        mfa_locked
      </code>
    </td>
    
    <td>
      依据 <code>
        Retry-After
      </code>
      
       等待；锁定或终止事务按提示重新开始
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        503 temporarily_unavailable
      </code>
    </td>
    
    <td>
      提供可重试状态或标准跳转登录入口
    </td>
  </tr>
</tbody>
</table>

业务日志只记录阶段、HTTP 状态与稳定错误码，不采集请求体或带授权码的 `redirect_url`。


## Sitemap

See the full [sitemap](https://docs.mcent.cn/sitemap.md) for all pages.
