# 认证与令牌

获取、刷新、吊销令牌，以及在业务请求中携带 Access Token。

import ApiDebugger from '../../../components/ApiDebugger.astro';

## 令牌生命周期

| 凭证          | 前缀   | 默认有效期 | 用途                                 |
| ------------- | ------ | ---------- | ------------------------------------ |
| App Key       | `ak_`  | 长期       | 标识应用，可在后台查看               |
| App Secret    | `sk_`  | 直到重置   | 证明应用身份，仅创建或重置时展示一次 |
| Access Token  | `oat_` | 24 小时    | 调用受保护接口                       |
| Refresh Token | `ort_` | 30 天      | 轮换 Access Token 和 Refresh Token   |

重置 App Secret 或禁用应用会立即吊销该应用的所有有效令牌。

## 获取令牌

| 项目 | 值 |
| --- | --- |
| 请求方法 | `POST` |
| 请求 URL | `https://backend.biotech-mkt.com/openapi/auth/token` |
| 认证方式 | App Key + App Secret |
| 权限要求 | 不需要接口授权 |
| Content-Type | `application/json` |

<ApiDebugger preset="issue-token" lang="zh" />

该接口不需要 Access Token。请求体必须是 JSON：

### 请求体

| 字段         | 类型   | 必填 | 限制            |
| ------------ | ------ | ---- | --------------- |
| `app_key`    | string | 是   | 最长 64 个字符  |
| `app_secret` | string | 是   | 最长 128 个字符 |

### 请求示例

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/token" \
  --header "Content-Type: application/json" \
  --data '{"app_key":"ak_xxx","app_secret":"sk_xxx"}'
```

### 响应示例

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "token_type": "Bearer",
    "app_id": 1,
    "app_key": "ak_xxx",
    "access_token": "oat_xxx",
    "expires_in": 86400,
    "access_expires_at": "2026-08-27 12:00:00",
    "refresh_token": "ort_xxx",
    "refresh_expires_in": 2592000,
    "refresh_expires_at": "2026-09-25 12:00:00"
  }
}
```

### 响应字段

| 字段                 | 返回类型 | 说明                                     |
| -------------------- | -------- | ---------------------------------------- |
| `token_type`         | string   | 固定为 `Bearer`                          |
| `app_id`             | integer  | OpenAPI 应用内部 ID                      |
| `app_key`            | string   | 当前应用的 App Key                       |
| `access_token`       | string   | Access Token，仅本次响应返回完整值       |
| `expires_in`         | integer  | Access Token 剩余有效秒数，默认 86400    |
| `access_expires_at`  | string   | Access Token 绝对过期时间                |
| `refresh_token`      | string   | Refresh Token，仅本次响应返回完整值      |
| `refresh_expires_in` | integer  | Refresh Token 剩余有效秒数，默认 2592000 |
| `refresh_expires_at` | string   | Refresh Token 绝对过期时间               |

成功时 `data` 包含 `token_type`、应用信息、两个令牌及各自的有效秒数和绝对过期时间。认证失败统一返回 `401`，不会区分 App Key 不存在还是 Secret 错误。

## 刷新令牌

| 项目 | 值 |
| --- | --- |
| 请求方法 | `POST` |
| 请求 URL | `https://backend.biotech-mkt.com/openapi/auth/refresh` |
| 认证方式 | Refresh Token |
| 权限要求 | 不需要接口授权 |
| Content-Type | `application/json` |

<ApiDebugger preset="refresh-token" lang="zh" />

### 请求体

| 字段            | 类型   | 必填 | 限制            |
| --------------- | ------ | ---- | --------------- |
| `refresh_token` | string | 是   | 最长 128 个字符 |

### 请求示例

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/refresh" \
  --header "Content-Type: application/json" \
  --data '{"refresh_token":"ort_xxx"}'
```

### 响应示例

刷新成功时返回字段与“获取令牌”一致，但 `access_token`、`refresh_token` 和过期时间均为新值：

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "token_type": "Bearer",
    "app_id": 1,
    "app_key": "ak_xxx",
    "access_token": "oat_new_xxx",
    "expires_in": 86400,
    "access_expires_at": "2026-08-27 13:00:00",
    "refresh_token": "ort_new_xxx",
    "refresh_expires_in": 2592000,
    "refresh_expires_at": "2026-09-25 13:00:00"
  }
}
```

刷新采用轮换机制：成功响应会同时返回新的 Access Token 和 Refresh Token，旧的两个令牌不能继续使用。

:::caution[原子替换]
客户端应串行执行同一个 Refresh Token 的刷新，并在一次事务或原子写入中替换两个令牌。不要让多个进程并发刷新同一个令牌。
:::

## 吊销令牌

| 项目 | 值 |
| --- | --- |
| 请求方法 | `POST` |
| 请求 URL | `https://backend.biotech-mkt.com/openapi/auth/revoke` |
| 认证方式 | Refresh Token |
| 权限要求 | 不需要接口授权 |
| Content-Type | `application/json` |

<ApiDebugger preset="revoke-token" lang="zh" />

### 请求体

| 字段            | 类型   | 必填 | 限制            |
| --------------- | ------ | ---- | --------------- |
| `refresh_token` | string | 是   | 最长 128 个字符 |

### 请求示例

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/revoke" \
  --header "Content-Type: application/json" \
  --data '{"refresh_token":"ort_xxx"}'
```

### 响应示例

```json
{
  "code": 200,
  "message": "成功",
  "data": []
}
```

吊销以 Refresh Token 为目标。重复提交或提交已经不存在的 Refresh Token 按幂等成功处理。

## 携带 Access Token

推荐使用标准 Bearer 请求头：

```http
Authorization: Bearer oat_xxx
```

兼容请求头：

```http
X-Openapi-Token: oat_xxx
```

当两个请求头同时存在且 Bearer 格式正确时，服务端优先使用 `Authorization`。不支持在 URL 查询参数中传递令牌。

## 认证响应头

令牌接口响应包含：

- `Cache-Control: no-store`
- `Pragma: no-cache`
- `X-Request-Id: <本次请求 ID>`

`401` 响应还包含 `WWW-Authenticate: Bearer`。

## 错误码与频控

认证接口可能返回 `401`、`422`、`429`、`500` 或 `503`。部署环境可以覆盖默认频控值；具体限制、`Retry-After` 和重试方式见[错误与频控](/guides/errors-rate-limits)。

## 安全建议

- App Secret 和 Refresh Token 只保存在服务端密钥系统或加密存储中。
- 日志只记录令牌前缀或不可逆指纹，不记录完整值。
- 使用 HTTPS，禁止通过明文 HTTP 传递任何凭证。
- Secret 泄露时立即在后台重置；重置会使现有令牌全部失效。
