Nginx 能否处理复杂的授权规则和请求重写?

Can Nginx handle complex authorization rules and request rewriting?

我使用第三方 REST API(我们称之为 FooService),它有几个缺点:

  1. 它不支持范围内的访问,即如果你可以进行身份​​验证,你就可以做任何事情。
  2. 他们的发布生命周期真的很短,而且他们不保证向后兼容性。

为了缓解这些问题,我想编写一个代理 API 它将:

  1. 实施一些范围内的访问规则,不仅针对每个端点,而且还根据请求参数使用一些相当复杂的逻辑。
  2. 明确实现我需要的端点并将请求传递给 FooService(即不是真正的反向代理)。如果 FooService 的未来版本有重大更改,我希望能够保留我自己的 API 签名,并在传递请求之前在内部将它们转换为 FooService 的新要求。我不需要的 FooService 端点不应该被我的新 API.
  3. 的消费者访问

我的第一直觉是用 Go 之类的语言编写一个新的应用程序。但是我想考虑使用 Nginx 配置来实现这个逻辑是否可行和有利。

可行吗?推荐的?如果是这样,你能给我指出一些示例代码,其中 Nginx:

  1. 根据程序逻辑授权用户;和
  2. 重写请求,重塑 URL、参数和正文。

P.S。它不一定是 Nginx;任何其他网络服务器都可以,只要它能满足我的需要。

我已经用 NGINX 和 NJS 实现了这个用例。

策略可以创建并存储在 JSON 文件或 Key-Value-Store 中,例如 etcd 或 hashicorps consul。

所以让我们做一个简单的例子。初始请求进来,你调用一个 auth_request 到一个 location 调用 njs 脚本 js_content.

在 njs 函数中,您可以获取策略(可以缓存 12 小时,每天只调用一次/两次后端)读取它并根据参数应用业务逻辑,例如 headers 和 uri.

策略检查后调用的操作在我这边,重写(它将再次使用新的uri开始处理),重定向(301、302)或路由(proxy_pass)到后端上游。

此设置已在 75K rps 下进行了实际测试,没有任何问题。长话短说:NJS 是通往这里的道路。

我不能公开分享整个代码,因为它还处理一些关键的授权部分,但如果需要我可以分享一些核心组件。

我用 nginx 做过类似的事情。
我有一个(弹性)搜索 UI,我希望能够访问企业搜索 API。 我想使用 google oauth 对用户进行身份验证,然后授予他们访问企业搜索 API 的权限,而不会泄露客户端的 API 机密。

我使用的机制是nginx子请求鉴权

nginx subrequest authentication

想法是某些endpoints/prefixes(即您的代理路径)由nginx 端的二次调用授权。根据该辅助呼叫的响应,原始呼叫将被代理(或不被代理)。所以你可以用一个小应用程序编写自己的 auth/access 逻辑。

这意味着对于对受保护端点的每次调用,nginx 都会发起(第一次)对您的 auth 端点的第二次调用,以确定是否应允许受保护的请求。只要您的“auth checker”应用程序快速且可扩展,这对大多数应用程序应该不会产生明显的性能影响(尤其是在 authcheck 是本地的且延迟很低的情况下)。

作为下面示例 nginx 配置的奖励,我还从 auth 端点返回一个秘密(在内部 authcheck 上......不暴露给客户端),然后将其作为 Bearer 令牌注入到受保护的代理请求中http 请求 header.

在内部身份验证位置,您可以将原始请求 url 作为 header (proxy_set_header X-Original-URI $request_uri;) 传递,以便根据参数等应用更多自定义逻辑。内部 auth 端点仍然只是返回“yes/no”(可能 headers 上的一些秘密将传递给代理端点)但是生成该决定的逻辑在您的内部可能更加复杂授权 app/endpoint.

# the protected (requires auth) upstream/proxy
upstream us_enterprise_search {
    server localhost:12345;
}

# the "auth" app
# can handle both user logins as well as the internal nginx access check
upstream us_app {
    server localhost:54321;
}

server {
    # ... usual stuff, listen port, tls certs, doc root for static file serving, gzip, cache config, etc

    #
    # now the interesting endpoints/prefixes...
    #

    # proxy to the enterprise-search API
    # the proxy/upstream we want to require authorization for
    location /api {
        # requests to /api need to be authenticated by making request to /auth/test
        # if a user has previously authenticated against the /auth endpoint, the /auth/test will grant access
        auth_request /auth/test;  
        auth_request_set $auth_status $upstream_status;
        auth_request_set $auth_token  $upstream_http_x_ent_auth;

        # the upstream/proxy we are protecting and requiring authentication against
        proxy_pass http://us_enterprise_search;
        proxy_pass_request_headers on;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;

        # set `authorization: Bearer search-XXXXXXXX` from upstream
        # i.e. take secret returned from the auth request and inject it on the proxy request
        proxy_set_header authorization "Bearer $auth_token";
        
    }

    # the internal (ie only nginx can access this, not a public endpoint) auth checking endpoint
    location = /auth/test {
        internal;  # so public can't access this
        # If the /auth/test subrequest returns a 2xx response code, the access is allowed. If it returns 401 or 403, the access is denied with the corresponding error code

        proxy_pass http://us_app;
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
        # pass original request_uri as header in auth request so
        # you can apply custom auth logic based on params
        proxy_set_header        X-Original-URI $request_uri;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_request_headers on;
    }

    # the login/logout UI app
    # accessible to public.  accessing this path enables the user to login or logout (establish auth) 
    # which will be reflected in the response to the internal /auth/test requests made by nginx
    # note this is the same app/upstream as /auth/test but doesn't need to be.
    # this app obviously has different logic/response for /auth (UI/oauth workflow) than for /auth/test
    location /auth {
        proxy_pass http://us_app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_request_headers on;
        
    }


}

如果你真的想要一个完整的包装纸API...

(避免使下游用户受到上游 API 更改的影响)

...那么您的包装器 API 应用程序 代理。所有对远程 API 的请求都需要 运行 通过您的应用程序,以便您在稳定的包装器 API 定义和不稳定的远程 API.

之间转换它们

在这种情况下,不需要复杂的nginx auth和secret传递。您仍然可以将 nginx 用作 api-wrapper-proxy 应用程序前面的 ssl-termination/loadbalancer 层...但它不需要做更多的事情。

这是否是个好主意取决于...

  • 上游 API 真正改变了多少?您是否能够可靠地 transform/map 旧 API signatures/inputs/outputs 他们所做的更改?重大的重大更改可能无法翻译,因为它们从根本上改变了业务逻辑或工作流。
  • 您可能需要监控上游 API changes/breaks 以便您可以快速将补丁应用到包装器以避免长时间中断或下游 API 用户出现意外行为。
  • build/monitor 和维护包装器可能需要做很多工作...值得吗? (价值对比 time/cost)

我个人不会这样做。根据您所处理的公司的规模以及您是否为服务支付大笔费用……我会尝试与技术领导层取得联系。解释一下,你会很感激他们在主要版本上修复他们的 api 签名,并且有过渡期,其中 API 的多个版本在最旧的版本生命周期结束之前并行 运行ning。如果您为这项服务支付了大量资金,您就有更大的影响力来吸引他们的注意力。

如果它是免费或 low-cost 服务,我可能不会构建包装器,而只是监视 API 何时损坏并拥有状态页面或推特机器人:“公司 X在没有通知的情况下更改了他们的 API 导致中断。我们正在努力适应变化,以便使服务重新上线。”您的用户知道这不是您的错,也许您可​​以羞辱公司,让他们在 API 稳定性方面做得更好。

或者更好的是,如果可以的话,找一个更稳定的 API 替代品。在没有警告的情况下更改 public api 签名表明他们的开发团队不是特别专业,因此幕后可能有很多“臭”技术,这些技术将来也会以其他方式咬你.