为所有用户缓存经过身份验证的请求


9

我正在开发一个Web应用程序,该应用程序必须处理需要授权的并发用户的巨大冲动,以请求相同的内容。在当前状态下,它甚至完全破坏了32核AWS实例。

(请注意,我们使用Nginx作为反向代理)

无法简单地缓存响应,因为在最坏的情况下,我们必须通过解码JWT来检查用户是否已通过身份验证。这要求我们启动Laravel 4 ,即使启用了PHP-FPM和OpCache ,它也很。这主要是由于繁重的自举阶段。

可能会问一个问题:“如果您知道这将成为一个问题,为什么首先要使用PHP和Laravel?” -但是现在再来做这个决定为时已晚!

可能的解决方案

提出的一种解决方案是将Auth模块从Laravel提取到一个轻量级的外部模块(以类似于C的方式编写),其职责是解码JWT并确定用户是否通过身份验证。

请求的流程为:

  1. 检查缓存是否命中(如果没有正常传递给PHP)
  2. 解码令牌
  3. 检查是否有效
  4. 如果有效,则从缓存中投放
  5. 如果无效,请告诉Nginx,然后Nginx将把请求传递给PHP进行正常处理。

一旦我们向单个用户提供了此请求,这将使我们不再打PHP,而是接触到轻量级模块来解决解码JWT以及此类auth附带的其他注意事项。

我什至在考虑直接将此代码作为Nginx HTTP扩展模块编写。

顾虑

我担心的是,我从未见过这样的事情,并且想知道是否有更好的方法。

同样,第二次您将任何用户特定的内容添加到页面,它完全杀死了该方法。

Nginx中是否可以直接使用另一种更简单的解决方案?还是我们必须使用像Varnish这样的更专业的东西?

我的问题:

上述解决方案有意义吗?

通常如何处理?

有没有更好的方法来实现类似或更好的性能提升?


我正在努力解决类似的问题。有两个想法:a)Nginx auth_request可以移交给您的身份验证微服务,从而减轻了开发Nginx模块的需要。b)或者,您的微服务可以将经过身份验证的用户重定向到一个公共的,可缓存的和不可猜测的临时URL,但是可以由PHP后端验证其在有限的时间段(缓存时间段)内有效。这会牺牲一些安全性,如果临时URL泄露给不受信任的用户,则他们可以在有限的时间内访问内容,就像OAuth Bearer令牌一样。
詹姆斯

您是否为此提出了解决方案?我正面临着同样的事情
timbroder '16

事实证明,通过拥有一个大型的优化后端节点集群,我们能够处理负载-但我对这种方法长期以来可以节省大量成本的解决方案充满信心。如果您知道可能会提前提供某些响应,并且在请求大量涌入之前对缓存进行了预热,则后端资源节省和可靠性提升将非常高。
iamyojimbo

Answers:


9

我一直在尝试解决类似的问题。我的用户需要针对他们的每个请求进行身份验证。我一直致力于通过后端应用程序(对JWT令牌的验证)至少对用户进行一次身份验证,但是在那之后,我决定不再需要后端了。

我选择避免要求默认不包含的任何Nginx插件。否则,您可以检查nginx-jwt或Lua脚本,这些可能是不错的解决方案。

身份验证

到目前为止,我已经完成了以下工作:

  • 使用将身份验证委托给Nginx auth_request。这将调用internal请求传递到我的后端令牌验证端点的位置。仅此一点根本解决不了处理大量验证的问题。

  • 令牌验证的结果使用proxy_cache_key "$cookie_token";指令进行缓存。成功验证令牌后,后端会添加一条Cache-Control指令,告诉Nginx仅将令牌缓存最多5分钟。此时,任何一次通过验证的身份验证令牌都在缓存中,来自同一用户/令牌的后续请求将不再接触身份验证后端!

  • 为了保护我的后端应用程序免遭无效令牌潜在的淹没,当我的后端端点返回401时,我还缓存了拒绝的验证。这些内容仅被缓存了很短的时间,以避免可能用此类请求填充Nginx缓存。

我添加了一些其他改进,例如注销端点通过返回401使令牌无效(该令牌也由Nginx缓存),以便如果用户单击注销,即使令牌未过期也无法使用。

另外,我的Nginx缓存包含每个令牌的关联用户作为JSON对象,这使我不必从数据库中获取该信息;并且还使我免于解密令牌。

关于令牌生存期和刷新令牌

5分钟后,令牌将在缓存中过期,因此将再次查询后端。这是为了确保令牌能够无效,因为用户已注销,令牌已被破坏等。这样的定期重新验证以及在后端的正确实现避免了我不得不使用刷新令牌的情况。

传统上,刷新令牌将用于请求新的访问令牌。它们将存储在您的后端中,并且您将验证是否使用刷新令牌发出对访问令牌的请求,该刷新令牌与该特定用户在数据库中拥有的刷新令牌匹配。如果用户注销,或者令牌遭到破坏,则您将在数据库中删除/禁用刷新令牌,以使使用无效的刷新令牌的下一个新令牌请求将失败。

简而言之,刷新令牌通常具有很长的有效性,并且始终根据后端进行检查。它们用于生成有效性很短(几分钟)的访问令牌。这些访问令牌通常确实会到达您的后端,但是您仅检查它们的签名和有效期。

在这里,在我的设置中,我们使用有效期更长的令牌(可以是数小时或一天),它们的角色和功能与访问令牌和刷新令牌相同。由于Nginx缓存了它们的验证和失效,因此后端每5分钟仅对其进行完全验证。因此,我们保留了使用刷新令牌(能够快速使令牌无效)的好处,而不会增加复杂性。而且,即使仅用于签名和有效期检查,简单验证也永远不会比Nginx缓存慢至少一个数量级的后端到达。

通过此设置,我可以在后端禁用身份验证,因为所有传入请求都auth_request在触摸Nginx指令之前到达该指令。

如果您需要执行任何类型的每资源授权,那并不能完全解决问题,但是至少您已经保存了基本授权部分。您甚至可以避免解密令牌或进行数据库查找以访问令牌数据,因为Nginx缓存的身份验证响应可以包含数据并将其传递回后端。

现在,我最大的担心是,我可能会打破一些与安全性相关的显而易见的东西而没有意识到。话虽如此,任何接收到的令牌在被Nginx缓存之前仍至少要经过一次验证。任何经过调整的令牌都将不同,因此不会命中缓存,因为缓存密钥也将有所不同。

另外,也许值得一提的是,真实世界的身份验证将通过生成(并验证)额外的Nonce或其他方式来对抗令牌窃取。

这是我的Nginx配置的简化摘录:

# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
    listen 443 ssl http2;
    server_name ........;

    include /usr/local/etc/nginx/include-auth-internal.conf;

    location /api/v1 {
        # Auth magic happens here
        auth_request         /auth;
        auth_request_set     $user $upstream_http_X_User_Id;
        auth_request_set     $customer $upstream_http_X_Customer_Id;
        auth_request_set     $permissions $upstream_http_X_Permissions;

        # The backend app, once Nginx has performed internal auth.
        proxy_pass           http://127.0.0.1:5000;
        proxy_set_header     X-User-Id $user;
        proxy_set_header     X-Customer-Id $customer;
        proxy_set_header     X-Permissions $permissions;

        # Cache content
        proxy_cache          content_cache;
        proxy_cache_key      "$request_method-$request_uri";
    }
    location /api/v1/Logout {
        auth_request         /auth/logout;
    }

}

现在,这是内部/auth端点的配置摘要,上面包括/usr/local/etc/nginx/include-auth-internal.conf

# Called before every request to backend
location = /auth {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_methods     GET HEAD POST;
    proxy_cache_key         "$cookie_token";
    # Valid tokens cache duration is set by backend returning a properly set Cache-Control header
    # Invalid tokens are shortly cached to protect backend but not flood Nginx cache
    proxy_cache_valid       401 30s;
    # Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
    proxy_cache_valid       200 5m;
    proxy_pass              http://127.0.0.1:1234/auth/_Internal;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        Accept application/json;
}

# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_key         "$cookie_token";
    # Proper caching duration (> token expire date) set by backend, which will override below default duration
    proxy_cache_valid       401 30m;
    # A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
    proxy_cache_bypass      1;

    # This backend endpoint always returns 401, with a cache header set to the expire date of the token
    proxy_pass              http://127.0.0.1:1234/auth/_Internal/Logout;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
}

处理内容服务

现在,身份验证与数据分离了。由于您告诉过每个用户相同的内容,因此内容本身也可以由Nginx缓存(在我的示例中为content_cache区域)。

可扩展性

假设您有一台Nginx服务器,此方案开箱即用。在现实世界中,您可能具有高可用性,这意味着多个Nginx实例,还可能托管您的(Laravel)后端应用程序。在这种情况下,您的用户发出的任何请求都可以发送到您的任何Nginx服务器,并且在他们全部本地缓存完令牌之前,他们将一直到达您的后端进行验证。对于少数服务器,使用此解决方案仍会带来很大的好处。

但是,需要注意的是,使用多个Nginx服务器(并因此缓存)时,您将无法在服务器端注销,因为您无法清除(通过强制刷新)所有令牌缓存,例如/auth/logout在我的例子中。您只剩下500万个令牌缓存持续时间,这将迫使您的后端很快被查询,并将告诉Nginx请求被拒绝。一种解决方法是注销时删除客户端上的令牌头或cookie。

任何评论将非常欢迎和赞赏!


您应该获得更多支持!非常有帮助,谢谢!
Gershon Papi

“我添加了一些其他改进,例如注销端点通过返回401使令牌无效(该令牌也由Nginx缓存),这样,如果用户单击注销,即使令牌没有过期也无法使用。 ” -这很聪明!,但实际上您是否也在后端将令牌列入黑名单,以防万一缓存失效或其他原因,用户仍然无法使用该特定令牌登录?
gaurav5430

“但是,重要的是要注意,使用多个Nginx服务器(并因此缓存),您将无法在服务器端注销,因为您无法清除(通过强制刷新)所有令牌缓存,就像/ auth / logout在我的示例中一样。” 你能详细说明吗?
gaurav5430
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.