Hyperlane 中的跨域与安全
引言
跨域资源共享(CORS)是现代 Web 浏览器中的一项基本安全机制。它控制哪些 Web 应用程序可以从不同来源访问资源,在防止未经授权的跨域请求方面发挥着关键作用。在使用 hyperlane 构建 API 和 Web 服务时,正确理解和配置 CORS 对于功能性和安全性都至关重要。
本文将介绍如何在 hyperlane 应用程序中实现 CORS,以及帮助保护你的 Web 服务的其他安全最佳实践。
理解 CORS
CORS 是浏览器强制执行的一种机制,限制网页向不同于其来源的域名发起请求。如果没有正确的 CORS 头部,浏览器会阻止跨域请求以保护用户免受潜在的安全威胁。
当请求目标的协议、域名或端口与当前页面不同时,就会发生"跨域"请求。例如:
https://api.example.com→https://api.example.com/v1(同源)https://example.com→https://api.example.com(跨域——不同子域名)http://example.com→https://example.com(跨域——不同协议)https://example.com:80→https://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 应用程序提供了全面的安全态势。