Nginx 能否处理复杂的授权规则和请求重写?
Can Nginx handle complex authorization rules and request rewriting?
我使用第三方 REST API(我们称之为 FooService),它有几个缺点:
- 它不支持范围内的访问,即如果你可以进行身份验证,你就可以做任何事情。
- 他们的发布生命周期真的很短,而且他们不保证向后兼容性。
为了缓解这些问题,我想编写一个代理 API 它将:
- 实施一些范围内的访问规则,不仅针对每个端点,而且还根据请求参数使用一些相当复杂的逻辑。
- 明确实现我需要的端点并将请求传递给 FooService(即不是真正的反向代理)。如果 FooService 的未来版本有重大更改,我希望能够保留我自己的 API 签名,并在传递请求之前在内部将它们转换为 FooService 的新要求。我不需要的 FooService 端点不应该被我的新 API.
的消费者访问
我的第一直觉是用 Go 之类的语言编写一个新的应用程序。但是我想考虑使用 Nginx 配置来实现这个逻辑是否可行和有利。
可行吗?推荐的?如果是这样,你能给我指出一些示例代码,其中 Nginx:
- 根据程序逻辑授权用户;和
- 重写请求,重塑 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 签名表明他们的开发团队不是特别专业,因此幕后可能有很多“臭”技术,这些技术将来也会以其他方式咬你.
我使用第三方 REST API(我们称之为 FooService),它有几个缺点:
- 它不支持范围内的访问,即如果你可以进行身份验证,你就可以做任何事情。
- 他们的发布生命周期真的很短,而且他们不保证向后兼容性。
为了缓解这些问题,我想编写一个代理 API 它将:
- 实施一些范围内的访问规则,不仅针对每个端点,而且还根据请求参数使用一些相当复杂的逻辑。
- 明确实现我需要的端点并将请求传递给 FooService(即不是真正的反向代理)。如果 FooService 的未来版本有重大更改,我希望能够保留我自己的 API 签名,并在传递请求之前在内部将它们转换为 FooService 的新要求。我不需要的 FooService 端点不应该被我的新 API. 的消费者访问
我的第一直觉是用 Go 之类的语言编写一个新的应用程序。但是我想考虑使用 Nginx 配置来实现这个逻辑是否可行和有利。
可行吗?推荐的?如果是这样,你能给我指出一些示例代码,其中 Nginx:
- 根据程序逻辑授权用户;和
- 重写请求,重塑 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 签名表明他们的开发团队不是特别专业,因此幕后可能有很多“臭”技术,这些技术将来也会以其他方式咬你.