在 OpenAPI 中定义 API 安全:安全方案完整指南与 Scalar 的实践
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
与真实世界一样,代码中总有需要保护的接口,因此你为它们配置了安全机制。但与真实世界不同的是,如何与这些安全机制交互往往并不直观——尤其是对 API 而言。这正是 OpenAPI 文档中定义安全的意义所在:它让你把已经落地的认证与授权机制描述清楚,帮助 API 的调用方理解安全要求,并以正确的方式访问受保护的部分。
这篇指南将系统讲解 OpenAPI 安全方案的几种类型、如何在 OpenAPI 文档中定义它们(securitySchemes与security的完整用法)、如何配合主流框架生成带安全定义的文档,并深入 Scalar 的开源实现,看 API 参考文档与 API 客户端是如何解析、呈现和携带这些安全凭证的。
关于「安全定义」这一旧称:安全方案(security schemes)这个概念,在 OpenAPI 2(原 Swagger)中被称为 security definitions(安全定义),如今在 OpenAPI 3+ 中统一命名为 security schemes。
OpenAPI 文档中可定义的安全类型
OpenAPI 文档是对真实 API 的机器可读描述,而真实 API 有各种各样的数据保护方式。你可以在文档中定义的安全类型包括:
- API Keys(API 密钥):可以通过请求头(header)、查询参数(query)或 Cookie 三种位置发送。
- HTTP 认证:包含 HTTP Basic(用户名 + 密码)以及 Bearer Token(如 JWT)。对于 Bearer 还可以定义
bearerFormat,例如标注为JWT。 - OAuth 2.0:支持多种授权流程(flows),从标准的重定向式授权码流程,到直接账号密码登录(password),再到应用级授权(client credentials)。每个流程都可以定义在哪里请求授权、在哪里用授权码换取令牌,以及可用的权限范围(scopes)。
- OpenID Connect:在 OAuth 2.0 之上构建,允许客户端通过一个 well-known 的发现端点(discovery endpoint)自动获取配置信息。
这些类型可以借助AND/OR逻辑自由组合,并且既可以全局应用,也可以只作用于单个操作(operation)。
在 Scalar 仓库的类型定义中,这四种方案被建模为SecuritySchemeObject的联合类型,对应 packages/openapi-types/src/openapi-types.ts 中的HttpSecurityScheme、ApiKeySecurityScheme、OAuth2SecurityScheme和OpenIdSecurityScheme。
如何定义 OpenAPI 安全:securitySchemes 与 security
定义 OpenAPI 安全从添加securitySchemes属性开始:它声明你的 API 支持的全部安全方案。随后用security将这些方案应用到全局或单个操作上。
可以把securitySchemes理解为「可用证件的类型清单」(护照、驾照、工牌),而security则指定「进入时需要出示哪些证件」。
定义安全方案(securitySchemes)
例如,希望你的 API 文档中同时支持BasicAuth或ApiKeyAuth,第一步是在components下定义它们:
components: securitySchemes: BasicAuth: type: http scheme: basic ApiKeyAuth: type: apiKey in: header name: X-API-Key全局应用安全方案(security)
定义好securitySchemes后,在文档顶层添加security即可全局应用。下面的示例表示整个 API 都接受「API 密钥或 Basic 认证」——注意数组中的每一项是「或」(OR)关系,调用方只要满足其中任意一个方案即可:
security: - ApiKeyAuth: [] - BasicAuth: [] components: securitySchemes: # ... rest of your OpenAPI doc如果只想全局强制使用BasicAuth,文档顶层只需要一个条目:
security: - BasicAuth: [] components: securitySchemes: BasicAuth: type: http scheme: basic description: Basic Authentication using username and password # ... rest of your OpenAPI file按操作应用安全方案
要让某个操作单独使用另一种安全方案,在对应 path 的 operation 上声明security即可,它会覆盖(而不是叠加)全局设置。下面这个示例中,POST /orders这个创建订单的操作单独要求ApiKeyAuth:
components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: API key for protected endpoints paths: /orders: post: summary: Create new order description: Protected endpoint - requires API key security: - ApiKeyAuth: [] # This operation requires API key responses: '201': description: Order created successfully content: application/json: schema: type: object properties: orderId: type: integer status: type: string '401': description: Unauthorized - invalid or missing API key # ... rest of your OpenAPI file各类安全方案的专属属性
除type和可选的description外,每种安全方案还有自己的专属字段:
apiKey类型:in(取值header、query、cookie)和name(凭证所在的请求头名、参数名或 Cookie 名);http类型:scheme(如basic、bearer)和bearerFormat(如JWT);oauth2类型:flows,其下包含authorizationCode、implicit、password、clientCredentials四种流程,各自拥有authorizationUrl、tokenUrl、refreshUrl、scopes等属性;openIdConnect类型:openIdConnectUrl(发现端点地址)。
这些字段的约束与 Scalar 仓库中的类型定义一一对应:例如HttpSecurityScheme包含scheme与bearerFormat,ApiKeySecurityScheme包含name与in,而OAuth2SecurityScheme的flows由四种 flow 类型组成(见 packages/openapi-types/src/openapi-types.ts)。
当然,另一面也要提醒:OpenAPI 文档只是底层真实 API 的「表示层」,你仍然需要在服务端真正实现这些安全机制,文档并不会替你保护任何端点。
组合与覆盖的语义
理解security的语义对写出正确文档至关重要:
- 顶层
security是全局默认值,路径级别的security会覆盖它; security数组中的每一项是一个 security requirement(安全要求对象),数组元素之间是OR关系;- 单个 requirement 对象内部可以包含多个方案键(如
- {ApiKeyAuth: [], BasicAuth: []}),同一对象内的多个键是AND关系,调用方必须同时满足全部方案; - 想取消全局安全要求,可以在操作上写
security: []。
Scalar 的 API 客户端在解析这些语义时做了非常细致的处理:在 packages/api-client/src/v2/blocks/scalar-auth-selector-block/helpers/security-scheme.ts 中,formatSecurityRequirement会先检查 requirement 的键数量——多键(AND 组合)会被格式化为「A & B」形式的复杂方案;单键则取对应方案格式化。同时,被x-scalar-ignore标记为隐藏的方案(无论是独立方案还是 AND 组合中的一部分)都会从认证 UI 中剔除,避免出现「无法配置的半隐藏组合」。
用你的框架定义安全
现代 API 开发框架的一大好处是能为你生成 OpenAPI 文档,因此它们通常也提供了便捷的安全定义方式。下面看三种常见框架的写法。
Hono(TypeScript)
使用zod-openapi时,先向文档注册安全方案,再在路由上应用它:
// Register the security scheme in your OpenAPI docs app.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', { type: 'http', scheme: 'bearer', description: 'Enter your JWT token in the format: Bearer <token>', }) // Apply the security to your routes const route = createRoute({ method: 'get', path: '/protected-resource', security: [{ Bearer: [] }], handler: async (c) => { const token = c.req.header('Authorization') // Token validation logic here return c.json({ message: 'Protected data' }) }, }).NET(ASP.NET Core)
在Swashbuckle中通过AddSecurityDefinition定义 Bearer 方案,并用AddSecurityRequirement把它作为全局安全要求挂上,再在端点上使用[Authorize]强制认证:
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement {{ new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() }}); }); // Apply authentication to your endpoints app.MapGet("/protected", [Authorize] () => "This endpoint requires authentication") .WithOpenApi();FastAPI(Python)
利用 FastAPI 的HTTPBasic依赖注入实现 Basic 认证,并借助secrets.compare_digest做常数时间比较(避免时序侧信道):
from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials import secrets app = FastAPI( title="Protected API", description="API secured with HTTP Basic Auth" ) security = HTTPBasic() def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)): # In production, use secure password comparison correct_username = secrets.compare_digest(credentials.username, "admin") correct_password = secrets.compare_digest(credentials.password, "secret") if not (correct_username and correct_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials", headers={"WWW-Authenticate": "Basic"}, ) return credentials.username @app.get("/secure-data/", summary="Get protected data", responses={ 401: {"description": "Invalid credentials"}, 200: {"description": "Successfully authenticated"} }) def secure_data(username: str = Depends(verify_credentials)): return {"message": f"Hello, {username}"}从这里也能看出「框架自动生成 OpenAPI 文档」的价值所在。例如 Django 默认并不自动生成 OpenAPI 文档,这会让它对接 API 参考工具或调试变得困难得多。
Scalar 如何处理安全方案
定义安全方案的终极目的是让 API 用起来更容易,这意味着 API 参考文档和 API 客户端都需要真正理解 OpenAPI 提供的各种安全方案。
API 参考文档(API Reference)
Scalar 的 API 参考文档支持 API Key、HTTP 与 OAuth 2.0 三类安全方案。基于你的 OpenAPI 文档以及应用到各操作上的安全方案,用户可以从中选择一种并携带它发送请求。
在幕后,Scalar 处理了以下逻辑(对应 packages/api-reference/src/features/Operation/helpers/get-required-security.ts 与 filter-selected-security.ts 等实现):
- 为每个操作正确匹配应当使用的方案(区分全局
security与操作级覆盖); - 提供收集所需信息的 UI(凭证输入、OAuth 授权交互);
- 在用户发起的请求上正确设置对应的认证方式(请求头、查询参数、Cookie、Authorization 头等);
- 管理认证状态(state);
- 处理因凭证无效或认证失败产生的错误。
API 客户端(API Client)
API 客户端的功能与参考文档非常相似,最大的差异在于:客户端的配置状态是私有的,因此可以在会话之间、集合(collection)之间持久化保存。这也意味着你可能同时拥有多个集合以及多个激活的安全方案。
为此,客户端采用了三层设计(实现集中在 packages/api-client/src/v2/blocks/scalar-auth-selector-block 目录):
- 集合级存储认证值:认证信息挂在 collection 上,随集合一起保存;
- 支持操作级覆盖:单个请求可以覆盖集合默认值;
- 按请求选择方案:每次请求都允许你切换要使用的安全方案。
方案选择器(selector)的交互性也更强:可以编辑 API Key 的 name 字段,也可以直接删除某个方案。在 security-scheme.ts 的getSecuritySchemeOptions中可以看到,选项列表被组织为「Required authentication(必需认证)」「Available authentication(可用认证)」分组,其中必需的方案来自文档的security声明,可用的方案则来自securitySchemes中未被必需化且未被隐藏的条目;在允许新增认证时(canAddNewAuth),还会附加一组「Add new authentication」预设选项。
客户端还会尽可能多地预填信息:
- 自动检测安全方案:从解析出的 OpenAPI 文档中识别全部
securitySchemes; - 按方案类型创建默认值结构:例如 API Key 默认填入
in与name,HTTP Bearer 默认scheme: bearer; - 预配置 OAuth 流程:如果文档提供了 flow 信息(授权 URL、令牌 URL、scopes),直接带入配置。
这些「可新增认证」的预设结构定义在 auth-options.ts 中,涵盖 API Key 的三种位置(Header、Query、Cookie)、HTTP Basic、HTTP Bearer,以及 OAuth 2.0 的四种流程(implicit、password、clientCredentials、authorizationCode,后者还支持 PKCE 选项x-usePkce)。
另外值得一提的是,OAuth 2.0 方案与 Bearer 方案之间存在智能联动:getOauth2AcquisitionTarget(见 security-scheme.ts)会在 Bearer 表单上提供「通过 OAuth2 授权」的快捷入口——优先选择带authorizationCode流程的方案(因为它支持刷新令牌),找不到时才退回仅提供implicit流程的方案。
安全与开发者体验的平衡
与 Scalar 的许多功能一样,团队持续在改进 OpenAPI 文档、API 参考文档与 API 客户端三者之间的联动。例如近期新增了在 API 参考与 API 客户端之间同步认证设置的能力:API 参考文档可以把认证设置传递给客户端弹窗,参考文档中选中的安全方案会成为客户端中的默认方案,省去重复配置。
安全不应该以牺牲开发者体验为代价。Scalar 提供的这些工具能帮助开发者快速、正确地完成认证,进而更顺畅地测试端点、排查问题,最终构建出更好也更安全的 API。
Mar 26, 2025
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考