# 在 Sisk 中启用 CORS（跨域资源共享）

Source: https://docs.sisk-framework.org/zh-cn/docs/features/cors.html

Sisk 提供了一个工具，可在公开服务时处理[跨域资源共享 (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)。该功能并非 HTTP 协议的一部分，而是由 W3C 定义的浏览器特性。此安全机制阻止网页向提供该网页的域之外的域发起请求。服务提供者可以允许特定域或单个域访问其资源。

## Same Origin

要将资源标识为“同源”，请求必须在其请求中包含 [Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) 头部：

```http
GET /api/users HTTP/1.1
Host: example.com
Origin: http://example.com
...
```

远程服务器必须在响应中返回一个与请求的 Origin 值相同的 [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) 头部：

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
...
```

此验证是**显式**的：主机、端口和协议必须与请求的完全一致。请查看下面的示例：

- 服务器返回其 `Access-Control-Allow-Origin` 为 `https://example.com`：
    - `https://example.net` - 域名不同。
    - `http://example.com` - 协议不同。
    - `http://example.com:5555` - 端口不同。
    - `https://www.example.com` - 主机不同。

在规范中，两种头部仅允许使用语法形式，无论是请求还是响应。URL 路径会被忽略。默认端口（HTTP 为 80，HTTPS 为 443）也会被省略。

```http
Origin: null
Origin: <scheme>://<hostname>
Origin: <scheme>://<hostname>:<port>
```

## Enabling CORS

在原生环境中，你可以在 [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) 中使用 [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) 对象。

可以在初始化服务器时配置 CORS：

```csharp
static async Task Main(string[] args)
{
    using var app = HttpServer.CreateBuilder()
        .UseCors(new CrossOriginResourceSharingHeaders(
            allowOrigin: "http://example.com",
            allowHeaders: ["Authorization"],
            exposeHeaders: ["Content-Type"]))
        .Build();

    await app.StartAsync();
}
```

上述代码会为**所有响应**发送以下头部：

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Headers: Authorization
Access-Control-Expose-Headers: Content-Type
```

这些头部必须在对 Web 客户端的所有响应中发送，包括错误和重定向。

你可能注意到 [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) 类有两个相似的属性： [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) 和 [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md)。请注意，一个是复数形式，另一个是单数形式。

- **AllowOrigin** 属性是静态的：仅会发送你指定的单一来源，适用于所有响应。
- **AllowOrigins** 属性是动态的：服务器会检查请求的来源是否在此列表中。如果匹配，则在该来源的响应中发送对应的头部。

当允许的来源取决于请求时（即使用 **AllowOrigins** 或下面描述的 [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) 值），Sisk 还会发送 `Vary: Origin` 头部，以防缓存将响应复用于不同的来源。

### Wildcards and automatic headers

或者，你可以在响应的来源中使用通配符 (`*`) 来表示允许任何来源访问资源。但该值不适用于带有凭证（授权头部）的请求，此操作会[导致错误](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials)。

可以通过显式列出允许的来源（使用 [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) 属性），或在 [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) 的值中使用 [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) 常量来规避此问题。该魔法属性会将 `Access-Control-Allow-Origin` 头部设置为请求的 `Origin` 头部的相同值。

你还可以使用 [AutoFromRequestMethod](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestMethod.md) 和 [AutoFromRequestHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestHeaders.md) 实现类似 `AllowOrigin` 的行为，自动根据发送的头部作出响应。

```csharp
using var host = HttpServer.CreateBuilder()
    .UseCors(new CrossOriginResourceSharingHeaders(
        
        // 根据请求的 Origin 头部作出响应
        allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin,
        
        // 根据 Access-Control-Request-Method 头部或请求方法作出响应
        allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod],

        // 根据 Access-Control-Request-Headers 头部或发送的头部作出响应
        allowHeaders: [CrossOriginResourceSharingHeaders.AutoFromRequestHeaders],

        exposeHeaders: [HttpKnownHeaderNames.ContentType, "X-Authenticated-Account-Id"],
        allowCredentials: true))
    .Build();
```

## Other Ways to Apply CORS

如果你在使用[服务提供者](https://docs.sisk-framework.org/zh-cn/docs/extensions/service-providers.md)，可以覆盖配置文件中定义的值：

```csharp
static async Task Main(string[] args)
{
    using var app = HttpServer.CreateBuilder()
        .UsePortableConfiguration(...)
        .UseCors(cors => {
            // 将覆盖配置文件中定义的来源
            // 文件。
            cors.AllowOrigin = "http://example.com";
        })
        .Build();

    await app.StartAsync();
}
```

## Disabling CORS on Specific Routes

`UseCors` 属性可用于路由及所有路由属性，并可通过以下示例禁用：

```csharp
[RoutePrefix("api/widgets")]
public class WidgetController : Controller {

    // GET /api/widgets/colors
    [RouteGet("/colors", UseCors = false)]
    public IEnumerable<string> GetWidgets() {
        return new[] { "Green widget", "Red widget" };
    }
}
```

## Replacing Values in the Response

可以在路由操作中显式替换或移除头部值：

```csharp
[RoutePrefix("api/widgets")]
public class WidgetController : Controller {

    public IEnumerable<string> GetWidgets(HttpRequest request) {

        // 移除 Access-Control-Allow-Credentials 头部
        request.Context.OverrideHeaders.AccessControlAllowCredentials = string.Empty;
        
        // 替换 Access-Control-Allow-Origin
        request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com";

        return new[] { "Green widget", "Red widget" };
    }
}
```

## Preflight Requests

预检请求是客户端在实际请求之前发送的[OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/OPTIONS) 方法请求。

当 `OPTIONS` 方法的请求匹配某条路由的路径，但该路径没有显式配置 `OPTIONS` 路由时，Sisk 服务器会返回 `200 OK` 并附带相应的 CORS 头部，随后客户端即可继续发送实际请求。

- 如果路由使用 [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) 显式映射，例如使用 `MapOptions`，则该路由处理请求，而不是自动响应。
- 如果匹配路径的路由设置了 `UseCors = false`，仍会发送自动响应，但不包含 CORS 头部。

> [!NOTE]
> 在 Sisk 1.7 之前，自动预检响应始终携带 CORS 头部，即使路由设置了 `UseCors = false`。

## Disabling CORS Globally

这在技术上不可行。若不想使用 CORS，只需不要进行配置。
