CORS-and-Security[20260825053459]

0 阅读1分钟

Hyperlane 中的跨域与安全

项目代码:github.com/hyperlane-d…

引言

跨域资源共享(CORS)是现代 Web 浏览器中的一项基本安全机制。它控制哪些 Web 应用程序可以从不同来源访问资源,在防止未经授权的跨域请求方面发挥着关键作用。在使用 hyperlane 构建 API 和 Web 服务时,正确理解和配置 CORS 对于功能性和安全性都至关重要。

本文将介绍如何在 hyperlane 应用程序中实现 CORS,以及帮助保护你的 Web 服务的其他安全最佳实践。

理解 CORS

CORS 是浏览器强制执行的一种机制,限制网页向不同于其来源的域名发起请求。如果没有正确的 CORS 头部,浏览器会阻止跨域请求以保护用户免受潜在的安全威胁。

当请求目标的协议、域名或端口与当前页面不同时,就会发生"跨域"请求。例如:

  • https://api.example.comhttps://api.example.com/v1(同源)
  • https://example.comhttps://api.example.com(跨域——不同子域名)
  • http://example.comhttps://example.com(跨域——不同协议)
  • https://example.com:80https://example.com:8080(跨域——不同端口)

Hyperlane 中的基础 CORS 配置

设置 CORS 头部

Hyperlane 使得在响应上配置 CORS 头部变得简单直接:

ctx.get_mut_response()
    .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
    .set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
    .set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);

这个配置:

  • ACCESS_CONTROL_ALLOW_ORIGIN — 指定允许哪些来源。WILDCARD_ANY*)允许所有来源。
  • ACCESS_CONTROL_ALLOW_METHODS — 列出允许的 HTTP 方法。ALL_METHODS 允许所有标准方法。
  • ACCESS_CONTROL_ALLOW_HEADERS — 指定允许哪些请求头。WILDCARD_ANY 允许所有头部。

将 CORS 作为中间件

为了在所有路由上保持一致的 CORS 配置,将其实现为中间件:

struct CorsMiddleware;

impl ServerHook for CorsMiddleware {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        ctx.get_mut_response()
            .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
            .set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
            .set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);

        Status::Continue
    }
}

server.request_middleware::<CorsMiddleware>();

通过注册这个中间件,你服务器上的每个响应都会包含适当的 CORS 头部。

CORS 配置选项

限制允许的来源

虽然 WILDCARD_ANY 在开发和公共 API 中很方便,但生产应用程序应该限制允许的来源:

struct RestrictedCorsMiddleware;

impl ServerHook for RestrictedCorsMiddleware {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        let origin = ctx.get_request()
            .try_get_header_back(ORIGIN)
            .unwrap_or_default();

        // 只允许特定来源
        if origin == "https://example.com" || origin == "https://app.example.com" {
            ctx.get_mut_response()
                .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, &origin);
        }

        ctx.get_mut_response()
            .set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE")
            .set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization");

        Status::Continue
    }
}

这种方法根据白名单验证 Origin 头部,只回显已批准的来源。

限制允许的方法

你可以限制跨域请求允许使用的 HTTP 方法:

ctx.get_mut_response()
    .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
    .set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST")
    .set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);

这将跨域请求限制为仅 GET 和 POST 方法。

限制允许的头部

同样,你可以控制允许哪些请求头:

ctx.get_mut_response()
    .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
    .set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
    .set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization, X-Requested-With");

这明确列出了客户端在跨域请求中允许发送的头部。

预检请求

理解预检

当跨域请求包含自定义头部或使用 GET、POST、HEAD 以外的方法(或特定的内容类型)时,浏览器会在实际请求之前发送一个"预检" OPTIONS 请求。这个预检请求向服务器请求许可。

处理预检请求

你可以在路由处理器中通过检查 OPTIONS 方法来处理预检请求:

impl ServerHook for ApiHandler {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        // 处理预检请求
        if ctx.get_request().get_method() == &RequestMethod::Options {
            ctx.get_mut_response()
                .set_status_code(204)
                .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
                .set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS")
                .set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization")
                .set_header(ACCESS_CONTROL_MAX_AGE, "86400");

            let data = ctx.get_mut_response().build();
            stream.try_send(data).await;
            return Status::Continue;
        }

        // 处理实际的 API 请求...
        Status::Continue
    }
}

Access-Control-Max-Age 头部告诉浏览器预检响应可以缓存多长时间(以秒为单位),减少预检请求的数量。

安全最佳实践

身份认证与 CORS

在将身份认证与 CORS 结合时,确保即使在身份认证失败响应上也设置 CORS 头部。这使客户端的浏览器能够正确读取错误:

struct SecureCorsAuthMiddleware;

impl ServerHook for SecureCorsAuthMiddleware {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        // 始终首先设置 CORS 头部
        ctx.get_mut_response()
            .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
            .set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
            .set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);

        // 然后执行身份认证
        let auth_str = ctx.get_request()
            .try_get_header_back(AUTHORIZATION)
            .unwrap_or_default();

        if auth_str.is_empty() {
            let data = ctx.get_mut_response()
                .set_status_code(401)
                .set_body("Unauthorized")
                .build();

            if stream.try_send(data).await.is_err() {
                stream.set_closed(true);
            }

            return Status::Reject;
        }

        Status::Continue
    }
}

连接管理的安全性

正确的连接管理是一个重要的安全考虑因素。在不再需要时始终关闭连接:

stream.set_closed(true);

并尊重客户端的 keep-alive 设置:

let keep_alive = stream.is_keep_alive(ctx.get_request().is_enable_keep_alive());
while stream.try_get_http_request().await.is_ok() {
    if !ctx.get_request().is_enable_keep_alive() {
        stream.set_closed(true);
        break;
    }
}

输入验证

始终验证和清理传入的请求数据。Hyperlane 提供了安全的方法来访问请求数据:

let method = ctx.get_request().get_method();
let path = ctx.get_request().get_path();
let host = ctx.get_request().get_host();
let headers = ctx.get_request().get_headers();
let body = ctx.get_request().get_body_string();
let json_body: T = ctx.get_request().get_body_json::<T>();
let query = ctx.get_request().try_get_query("key");

使用 try_get_* 方法而不是 get_* 方法有助于防止因缺少数据而导致的恐慌。

请求大小限制

通过 RequestConfig 配置请求大小限制,防止拒绝服务攻击:

let request_config_json = r#"{
    "buffer_size": 8192,
    "max_path_size": 8192,
    "max_header_count": 100,
    "max_header_key_size": 8192,
    "max_header_value_size": 8192,
    "max_body_size": 2097152,
    "read_timeout_ms": 6000
}"#;

let request_config = RequestConfig::from_json(request_config_json).unwrap();
let mut server: Server = Server::from(request_config);

关键的安全相关设置:

  • max_body_size — 限制最大请求体大小(本例中为 2MB)。
  • max_header_count — 限制每个请求的头部数量。
  • max_path_size — 限制 URL 路径长度。
  • read_timeout_ms — 设置读取请求的超时时间,防止慢速拒绝服务攻击。

超时保护

实现超时中间件以防止长时间运行的请求耗尽资源:

spawn(async move {
    timeout(Duration::from_millis(100), async move {
        new_ctx.get_mut_response().set_status_code(504).set_body("timeout");
    }).await.unwrap();
});

CORS 和路由过滤器

Hyperlane 的路由过滤器系统可用于对不同路由应用不同的 CORS 策略:

#[host("api.example.com")]
struct ApiRoute;

#[reject_host("blocked.example.com")]
struct SafeRoute;

这些过滤器在请求到达你的处理器之前运行,提供了额外的安全层。

完整的安全服务器示例

以下是一个完整的示例,展示了具有 CORS、身份认证和正确错误处理的安全服务器配置:

#[tokio::main]
async fn main() {
    let mut server: Server = Server::default();

    // 注册 CORS 中间件
    server.request_middleware::<CorsMiddleware>();

    // 注册身份认证中间件
    server.request_middleware::<AuthMiddleware>();

    // 注册路由
    server.route::<ApiHandler>("/api/data");

    let server_control_hook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
}

总结

CORS 和安全是使用 hyperlane 构建 Web 应用程序时需要考虑的基本要素。通过正确配置 CORS 头部、实现身份认证中间件、验证输入、设置请求大小限制以及仔细管理连接,你可以构建安全且功能完善的 Web 服务。

请记住,CORS 是浏览器端的安全机制——它不能阻止服务器端访问你的 API。无论你的 CORS 配置如何,始终在服务器上实现适当的身份认证和授权。CORS、身份认证中间件、输入验证和连接管理的结合为你的 hyperlane 应用程序提供了全面的安全态势。


项目代码:github.com/hyperlane-d…