# 如何使用 SMART on FHIR 实现患者同意

> 教程：SMART on FHIR 结合 OAuth 2.1 和 PKCE 分步实现 — 发现、应用注册、同意屏幕、令牌交换、作用域强制执行、刷新轮换和撤销 — 以及 Anpheros 的实现方式。

Source: https://developers.anpheros.com/zh/guides/smart-on-fhir-backend

**SMART on FHIR 是应用程序获取个人访问其 FHIR 记录权限的标准方式：使用 OAuth 2.1 和 PKCE 进行授权，使用作用域精确说明可读写的内容，以及使用发现文档告知应用程序去哪里。** 本教程将分步演示整个流程在 Anpheros 上的运行情况，包括 `curl`：发现、注册应用程序、将用户引导至同意屏幕、交换代码、在授予的作用域内调用 API、刷新以及用户撤销访问时发生的情况。Anpheros 是 SMART on FHIR 后端：它托管授权服务器、同意屏幕和 FHIR R4 服务器，因此您无需编写自己的登录或同意 UI。

## 为什么使用同意书而不是 API 密钥

API 密钥只能访问您的项目创建的患者数据。个人的现有记录（他们自己的数据，由其他应用程序、诊所和实验室收集）由他们自己决定是否共享。使用 SMART on FHIR：

- 用户能以他们自己的语言看到，哪个应用程序在请求什么，它想读取或写入什么，目的是什么，以及持续多久；
- 您的应用程序只能获取那些资源类型和操作，并且仅限于该一个记录；
- 每次访问都会被记录并对用户可见，用户可以随时撤销访问权限；
- 授权也会以 FHIR `Consent` 资源的形式存储。

其背后的模型在 [带有患者同意书的医疗保健 API](https://developers.anpheros.com/zh/guides/healthcare-api-patient-consent) 和 [同意书和访问模型](https://developers.anpheros.com/zh/guides/consent) 中有详细描述。

## 步骤 1：发现

```bash
curl https://platform.anpheros.com/fhir/R4/.well-known/smart-configuration
```

响应列出了端点以及服务器支持的内容（节选）：

```json
{"issuer": "https://platform.anpheros.com",
 "authorization_endpoint": "https://platform.anpheros.com/oauth/authorize",
 "token_endpoint": "https://platform.anpheros.com/oauth/token",
 "revocation_endpoint": "https://platform.anpheros.com/oauth/revoke",
 "code_challenge_methods_supported": ["S256"],
 "scopes_supported": ["patient/*.rs", "patient/Patient.r", "patient/Observation.rs", "patient/Observation.rs?category=laboratory",
                      "patient/Observation.rs?category=vital-signs", "patient/Observation.c", "patient/Condition.rs",
                      "patient/MedicationStatement.rs", "patient/DocumentReference.rs", "patient/AllergyIntolerance.rs",
                      "offline_access", "launch/patient", "openid", "fhirUser"],
 "capabilities": ["launch-standalone", "client-public", "client-confidential-symmetric", "permission-patient",
                  "permission-offline", "context-standalone-patient", "sso-openid-connect"]}
```

SMART 客户端库会读取此文档，无需其他配置。

## 步骤 2：注册您的应用程序

在 [仪表板](https://platform.anpheros.com/dashboard/) 中，打开您的项目并添加一个应用程序及其重定向 URI。或者通过 Admin API：

```bash
curl -X POST https://platform.anpheros.com/admin/projects/$PROJECT/applications \
     -H "Authorization: Bearer $FIREBASE_ID_TOKEN" -H "Content-Type: application/json" -d '{
  "name": "CardioAI", "client_type": "public",
  "redirect_uris": ["https://cardio.example/callback"],
  "purpose": "cardiology follow-up", "privacy_url": "https://cardio.example/privacy"
}'
```

```json
{"id": "app_…", "name": "CardioAI", "client_type": "public", "client_id": "client_Hg6CknNkYlMgN190", "client_secret": null,
 "redirect_uris": ["https://cardio.example/callback"], "purpose": "cardiology follow-up", "status": "active", …}
```

对于无法保管密钥并依赖 PKCE 的移动和浏览器应用，请使用 `public`。对于服务器，请使用 `confidential`；其 `client_secret` 仅显示一次。名称、目的和隐私链接是用户在同意屏幕上看到的内容。

## 步骤 3：将用户引导至同意屏幕

创建一对 PKCE。验证器保留在您这里；只有其哈希值会被传输：

```bash
VERIFIER=$(openssl rand -base64 64 | tr -d '=+/\n' | cut -c1-64)
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
```

然后，在用户的浏览器中打开此地址（为方便阅读添加了换行符）：

```
https://platform.anpheros.com/oauth/authorize?response_type=code
  &client_id=client_Hg6CknNkYlMgN190
  &redirect_uri=https://cardio.example/callback
  &scope=openid fhirUser patient/Observation.rs?category=laboratory patient/Condition.rs offline_access
  &state=8c1e…               ← a random value you check on return
  &code_challenge=$CHALLENGE&code_challenge_method=S256
  &purpose=treatment&lang=en
```

请求您最需要的信息。此应用程序读取实验室结果和诊断信息，仅此而已。`offline_access` 会提供一个刷新令牌，因此用户无需每 30 分钟重新批准一次。

用户登录 Anpheros，以通俗易懂的语言阅读请求，选择要共享的记录（他们自己的、他们照顾的家庭成员的，或您的应用程序为他们创建的记录）以及访问的持续时间，然后允许或拒绝。他们将返回到您的重定向 URI：

```
https://cardio.example/callback?code=…&state=8c1e…
```

在代码之前，请检查 `state` 是否与您之前发送的值匹配。

## 第 4 步：用代码交换令牌

```bash
curl -X POST https://platform.anpheros.com/oauth/token \
  -d grant_type=authorization_code -d client_id=client_Hg6CknNkYlMgN190 \
  -d code=$CODE -d code_verifier=$VERIFIER -d redirect_uri=https://cardio.example/callback
```

```json
{"access_token": "at_…", "token_type": "Bearer", "expires_in": 1800,
 "scope": "patient/Observation.rs?category=laboratory patient/Condition.rs openid fhirUser offline_access",
 "patient": "82bfd321-6a4d-4aea-943a-a62f4d81d419", "grant_id": "grant_…",
 "id_token": "eyJ…", "refresh_token": "rt_…"}
```

- `scope` 列出了已授予的内容。请将其与令牌一起保留，并仅显示其涵盖的功能。
- `patient` 是您应用程序的记录 ID。它是成对的：另一个应用程序在不同 ID 下看到同一个人。
- `id_token` 是一个 OpenID Connect 令牌，其 `fhirUser` claim 命名患者资源。
- 该代码只能使用一次，并且只能与匹配的 `code_verifier` 一起使用。

## 步骤 5：在作用域内调用 API

```bash
curl "https://platform.anpheros.com/fhir/R4/Observation?patient=$PATIENT&category=laboratory" -H "Authorization: Bearer $ACCESS_TOKEN"
```

实验室结果照常返回。请求超出授权范围的内容，例如药物：

```bash
curl "https://platform.anpheros.com/fhir/R4/MedicationStatement?patient=$PATIENT" -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json
{"resourceType": "OperationOutcome", "issue": [{"severity": "error", "code": "forbidden",
  "diagnostics": "The consent behind this token does not allow 's' on MedicationStatement."}]}
```

这是一个`403`。超出授权范围的搜索也会被过滤：使用`Observation.rs?category=laboratory`，搜索所有观察结果只会返回实验室相关的。对于此令牌，任何其他患者都不存在（`404`）。REST 视图（`/v1`）和[上下文 API](https://developers.anpheros.com/zh/guides/llm-healthcare-data) 应用相同的范围。

## 步骤 6：刷新

访问令牌有效期为 30 分钟。刷新令牌有效期为 30 天，并在每次使用时轮换：

```bash
curl -X POST https://platform.anpheros.com/oauth/token \
  -d grant_type=refresh_token -d client_id=client_Hg6CknNkYlMgN190 -d refresh_token=$REFRESH_TOKEN
```

您将获得一个新的访问令牌和一个新的刷新令牌。请立即存储新的令牌。重复使用旧的刷新令牌将被视为盗用：它将以 `400` 失败并撤销整个授权。

## 步骤 7：撤销

用户可以随时在 Anpheros 中撤销您的访问权限，您也可以自行撤销，例如当用户在您的应用中断开连接时：

```bash
curl -X POST https://platform.anpheros.com/oauth/revoke -d token=$REFRESH_TOKEN -d client_id=client_Hg6CknNkYlMgN190
```

从那时起，使用该授权的所有调用都将返回 `401`。通过向用户显示连接已结束并提供重新连接的选项来处理此情况。订阅 `consent.revoked` [webhook](https://developers.anpheros.com/zh/guides/webhooks) 可以在不等待调用失败的情况下获知此信息。

## 使用 SDK

[TypeScript 和 Dart SDK](https://developers.anpheros.com/zh/guides/sdks) 会为您处理步骤 3 到 6：`OAuthFlow.authorizeUrl` 使用生成的 PKCE 对，`exchange` 在回调时，以及自动刷新和轮换。完整示例：[如何使用 FHIR 后端构建 AI 医疗保健应用](https://developers.anpheros.com/zh/guides/build-ai-healthcare-app) (TypeScript) 和 [如何使用 Flutter 构建医疗保健应用](https://developers.anpheros.com/guides/healthcare-app-flutter) (Dart)。

## Anpheros 的实现

| SMART 的部分 | Anpheros 中的实现 |
|---|---|
| Discovery | `/.well-known/smart-configuration` on the FHIR base, with the endpoints, scopes and PKCE methods supported |
| Launch | standalone launch (应用程序启动，然后请求访问)；不支持从临床系统内部启动的 EHR launch |
| Authorisation | OAuth 2.1 with PKCE required, authorisation code flow |
| Scopes | SMART v2 patient scopes, `patient/<Resource>.<cruds>` with category filters, plus `openid` and `fhirUser` |
| Identity | an OpenID Connect `id_token` naming the patient as a FHIR resource |
| Consent | a hosted consent screen in the patient's language, with the duration chosen by the patient, mirrored as a FHIR `Consent` |
| Tokens | 30-minute access tokens, rotating 30-day refresh tokens bound to the consent, revocation |
| Conformance | the capability statement declares the SMART security service; the "Standalone Launch" group of the Inferno SMART App Launch STU2 test kit passes 22 of 22 tests (run of 28 September 2026); the other Inferno groups and test kits have not been run |

Writing works the same way: write scopes (`patient/Observation.c`, `.u`, `.d`) let your application create, update or delete within the record. Everything written carries your application as the author, retries with an idempotency key never duplicate a measurement, and deletions are soft and keep history ([Medical data API](https://developers.anpheros.com/zh/guides/medical-data-api)).

## 尚不支持的功能

- EHR 启动（从医院系统内部使用启动上下文打开的应用程序）。
- `system/` 范围的后端服务；组织使用 API 密钥代替。
- 批量数据导出（`$export`）；支持按患者导出（`$everything`）。

这些功能与其余功能一起列在 [版本控制和弃用](https://developers.anpheros.com/zh/guides/versioning) 中，能力声明始终是权威来源。

## 常见问题解答

### 什么是 SMART on FHIR？
SMART on FHIR 是一个开放标准，基于 OAuth 2.0/2.1 和 OpenID Connect 构建，它定义了应用程序如何请求访问 FHIR 记录，哪些范围描述了该访问，以及服务器如何宣传其端点。任何支持 SMART 的客户端库都可以使用任何支持 SMART 的服务器。

### 我需要 PKCE 吗？
是的。Anpheros 要求对所有授权码流程都使用 PKCE 和 `S256`，无论是公共客户端还是机密客户端。这可以防止截获的授权码被其他人使用。

### 如果用户拒绝怎么办？
用户将返回到您的重定向 URI，并附带 `error=access_denied` 和您的 `state`，但没有代码。请解释该功能的需求，并让他们重试；在授权之外的调用始终会返回 `403`。

### 家长可以为孩子提供同意吗？
可以。在同意屏幕上，用户可以选择他们关心的记录，例如孩子的记录，前提是该记录已链接到他们的账户。授权仅适用于该记录。

## 相关资源

- [身份验证和 OAuth](https://developers.anpheros.com/zh/guides/authentication)：API 密钥、令牌和生命周期详解
- [带有患者同意的医疗保健 API](https://developers.anpheros.com/zh/guides/healthcare-api-patient-consent)：同意如何改变您的应用程序设计
- [同意和访问模型](https://developers.anpheros.com/zh/guides/consent)
- [FHIR 平台](https://developers.anpheros.com/zh/guides/fhir)
- [构建医疗保健应用程序](https://developers.anpheros.com/zh/guides/build-a-healthcare-app)
