Authentication-Middleware[20261006163717]

0 阅读1分钟

Hyperlane 中的身份认证中间件

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

引言

身份认证是 Web 应用程序的关键安全问题。无论你是在构建 REST API、Web 应用程序还是微服务,控制谁可以访问你的资源都是至关重要的。在 hyperlane 框架中,身份认证通常作为中间件实现——在请求到达路由处理器之前拦截请求的可复用组件。

本文将探讨你可以在 hyperlane 中实现的各种身份认证模式,从简单的基于头部的检查到更复杂的令牌验证方案。

理解 Hyperlane 中的中间件

在深入身份认证之前,了解中间件在 hyperlane 中是如何工作的是有帮助的。中间件组件实现 ServerHook trait,它提供了两个关键方法:

  • new() — 在建立新连接时调用。用于初始化。
  • handle() — 对连接上的每个请求调用。这就是身份认证逻辑所在的位置。

handle() 方法返回一个 Status 枚举值:

  • Status::Continue — 允许请求继续到下一个处理器。
  • Status::Reject — 拒绝请求并停止进一步处理。

基础身份认证中间件

基于头部的身份认证

最简单的身份认证形式是检查 Authorization 头部的存在。以下是 hyperlane 文档中的基础身份认证中间件:

impl ServerHook for AuthMiddleware {
    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        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
    }
}

这个中间件:

  1. 从传入的请求中读取 Authorization 头部。
  2. 如果头部缺失或空,它以 401 Unauthorized 状态响应并拒绝请求。
  3. 如果头部存在,它允许请求继续。

使用 try_get_header_back

这里使用 try_get_header_back 方法是因为 Authorization 头部通常由客户端添加(从服务器角度看是"反向"的)。此方法返回 Option<String>,unwrap_or_default() 在头部不存在时将其转换为空字符串。

身份认证中的错误处理

注意发送拒绝响应时的仔细错误处理:

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

如果发送失败(例如,因为客户端已经断开连接),流被标记为已关闭,以防止进一步尝试写入。

Bearer Token 身份认证

验证 Bearer Token

一种常见的身份认证模式是 Bearer Token 身份认证,其中 Authorization 头部包含一个以 "Bearer" 为前缀的令牌:

struct BearerAuthMiddleware;

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

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

        if !auth_str.starts_with("Bearer ") {
            let data = ctx.get_mut_response()
                .set_status_code(401)
                .set_body("Unauthorized: Invalid token format")
                .build();

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

            return Status::Reject;
        }

        let token = &auth_str[7..]; // 提取 "Bearer " 之后的令牌

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

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

            return Status::Reject;
        }

        // 将令牌存储在上下文中,供下游处理器使用
        ctx.set_attribute("auth_token", token);

        Status::Continue
    }
}

server.request_middleware::<BearerAuthMiddleware>();

这个增强的中间件:

  1. 检查 Authorization 头部是否以 "Bearer " 开头。
  2. 提取实际的令牌值。
  3. 将令牌存储在上下文属性中,以便下游处理器可以访问。
  4. 为不同的失败场景返回适当的错误消息。

基于方法的身份认证

有时你只想允许特定的 HTTP 方法。Hyperlane 的属性宏使这变得简单:

#[methods("GET", "POST")]
struct MethodRestrictedRoute;

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

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        // 只有 GET 和 POST 请求能到达这里
        // 处理请求...
        Status::Continue
    }
}

#[methods] 属性会自动拒绝不匹配指定 HTTP 方法的请求,甚至在调用 handle() 方法之前。

路由级别的身份认证

使用路由过滤器

Hyperlane 的路由系统支持基于属性的过滤器,可用于身份认证和访问控制:

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

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

#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
struct GetOnlyRoute;

#[reject(ctx.get_request().get_path().len() > 1000)]
struct SafePathRoute;

这些过滤器提供不同级别的访问控制:

  • #[host] — 只允许对特定主机的请求。
  • #[reject_host] — 阻止来自特定主机的请求。
  • #[filter] — 应用自定义过滤表达式。
  • #[reject] — 拒绝匹配条件的请求。

基于 Referer 的过滤

你还可以基于 Referer 头部进行过滤:

#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
struct RefererFilteredRoute;

这对于防止热链接或阻止来自已知恶意域名的请求很有用。

将身份认证与其他中间件结合

身份认证和 CORS

在构建服务于跨域请求的 API 时,身份认证中间件必须与 CORS 中间件协同工作。无论身份认证是否成功,都应设置 CORS 头部:

struct CorsAuthMiddleware;

impl ServerHook for CorsAuthMiddleware {
    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
    }
}

使用优先级的多中间件

Hyperlane 允许你使用不同的优先级注册多个中间件:

#[request_middleware(1)]
struct RequestMiddleware1;

#[request_middleware(2)]
struct RequestMiddleware2;

较小的数字先执行。这让你可以控制身份认证和其他中间件的运行顺序。

使用上下文属性进行身份认证

Hyperlane 的上下文属性系统允许身份认证数据在中间件和路由处理器之间流动:

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

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        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;
        }

        ctx.set_attribute("authenticated", "true");
        ctx.set_attribute("auth_token", &auth_str);

        Status::Continue
    }
}

然后在你的路由处理器中:

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

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        let is_authenticated: Option<String> = ctx.try_get_attribute("authenticated");

        if is_authenticated.is_some() {
            // 用户已认证——提供受保护的资源
            ctx.get_mut_response()
                .set_status_code(200)
                .set_body("Protected content");
        } else {
            ctx.get_mut_response()
                .set_status_code(403)
                .set_body("Forbidden");
        }

        let data = ctx.get_mut_response().build();
        stream.try_send(data).await;

        Status::Continue
    }
}

注册身份认证中间件

身份认证中间件使用 request_middleware 方法注册到服务器:

server.request_middleware::<AuthMiddleware>();

对于基于属性宏的中间件:

#[request_middleware(1)]
struct AuthMiddleware;

中间件对到达它的每个请求运行,使其成为强制执行身份认证策略的理想场所。

身份认证中间件最佳实践

  1. 快速失败。 尽早拒绝未认证的请求,以避免不必要的处理。

  2. 提供清晰的错误消息。 告诉客户端请求被拒绝的原因(例如,"缺少令牌"、"令牌格式无效"、"令牌已过期")。

  3. 使用上下文属性传播令牌。 将验证后的令牌数据存储在上下文属性中,这样下游处理器就不需要重新解析 Authorization 头部。

  4. 在身份认证之前设置 CORS 头部。 对于 API,确保即使在错误响应中也存在 CORS 头部,以便客户端的浏览器可以读取错误。

  5. 优雅地处理流错误。 始终检查 try_send 的结果,如果失败则关闭流。

  6. 分层你的中间件。 使用优先级数字控制中间件执行顺序。CORS 中间件通常应在身份认证中间件之前运行。

  7. 不要在 Cookie 中存储 API 认证的敏感数据。 对 API 使用 Authorization 头部,为浏览器会话保留 Cookie。

总结

Hyperlane 提供了一个灵活且强大的中间件系统,用于在 Web 应用程序中实现身份认证。从简单的头部检查到复杂的令牌验证,ServerHook 让你完全控制身份认证过程。通过将身份认证中间件与上下文属性、路由过滤器和其他中间件组件结合,你可以构建安全且可维护的访问控制系统。

关键是利用 hyperlane 的中间件管道——以适当的优先级级别注册身份认证中间件,使用上下文属性传播身份认证状态,以及在整个过程中优雅地处理错误。


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