Bộ nhớ đệm yêu cầu xác thực cho tất cả người dùng


9

Tôi đang làm việc trên một ứng dụng web phải xử lý các xung lực rất lớn của người dùng đồng thời, những người cần được ủy quyền, để yêu cầu nội dung giống hệt nhau. Ở trạng thái hiện tại, nó hoàn toàn làm tê liệt ngay cả phiên bản AWS 32 lõi.

(Lưu ý rằng chúng tôi đang sử dụng Nginx làm proxy ngược)

Phản hồi không thể được lưu trữ đơn giản vì trong trường hợp xấu nhất, chúng ta phải kiểm tra xem người dùng có được xác thực hay không bằng cách giải mã JWT của họ. Điều này đòi hỏi chúng tôi phải kích hoạt Laravel 4, điều mà hầu hết đều đồng ý, là chậm , ngay cả khi đã bật PHP-FPM và OpCache. Điều này chủ yếu là do giai đoạn bootstrapping khổng lồ.

Người ta có thể đặt câu hỏi "Tại sao bạn lại sử dụng PHP và Laravel ngay từ đầu nếu bạn biết đây sẽ là một vấn đề?" - nhưng bây giờ đã quá muộn để quay lại quyết định đó!

Giải pháp có thể

Một giải pháp đã được đưa ra là trích xuất mô-đun Auth từ Laravel sang mô-đun bên ngoài nhẹ (được viết bằng thứ gì đó nhanh như C) có trách nhiệm giải mã JWT và quyết định xem người dùng có được xác thực hay không.

Luồng của một yêu cầu sẽ là:

  1. Kiểm tra xem bộ đệm có bị tấn công không (nếu không chuyển qua PHP như bình thường)
  2. Giải mã mã thông báo
  3. Kiểm tra xem nó có hợp lệ không
  4. Nếu hợp lệ , phục vụ từ bộ đệm
  5. Nếu không hợp lệ , hãy nói với Nginx, và sau đó Nginx sẽ chuyển yêu cầu tới PHP để xử lý như bình thường.

Điều này sẽ cho phép chúng tôi không gặp PHP một khi chúng tôi đã phục vụ yêu cầu này cho một người dùng và thay vào đó tiếp cận với một mô-đun nhẹ để giải quyết vấn đề JWT và bất kỳ cảnh báo nào khác đi kèm với loại xác thực này.

Tôi thậm chí đã nghĩ đến việc viết mã này trực tiếp như một mô-đun mở rộng Nginx HTTP.

Mối quan tâm

Mối quan tâm của tôi là tôi chưa bao giờ thấy điều này được thực hiện trước đây và tự hỏi liệu có cách nào tốt hơn không.

Ngoài ra, lần thứ hai bạn thêm bất kỳ nội dung cụ thể của người dùng vào trang, nó hoàn toàn giết chết phương pháp này.

Có một giải pháp đơn giản hơn có sẵn trực tiếp trong Nginx? Hay chúng ta sẽ phải sử dụng thứ gì đó chuyên dụng hơn như Varnish?

Những câu hỏi của tôi:

Liệu các giải pháp trên có ý nghĩa?

Làm thế nào là điều này thường được tiếp cận?

Có cách nào tốt hơn để đạt được hiệu suất tương tự hoặc tốt hơn không?


Tôi đang vật lộn với một vấn đề tương tự. Một vài ý tưởng a) Nginx auth numquest có thể cung cấp cho microservice xác thực của bạn, giảm nhu cầu phát triển mô-đun Nginx. b) Ngoài ra, microservice của bạn có thể chuyển hướng người dùng được xác thực đến một URL tạm thời công khai, có thể lưu trong bộ nhớ cache và không thể truy cập được, nhưng có thể được xác nhận bởi phụ trợ PHP có hiệu lực trong một khoảng thời gian giới hạn (thời gian bộ đệm). Điều này hy sinh một số bảo mật, nếu URL tạm thời bị rò rỉ cho người dùng không đáng tin cậy, họ có thể truy cập nội dung trong khoảng thời gian giới hạn đó, giống như Mã thông báo OAuth Bearer.
Gia-cơ

Bạn đã đưa ra một giải pháp cho điều này? Tôi đang đối mặt với điều tương tự
đếm thời gian

Hóa ra bằng cách có một cụm lớn các nút phụ trợ được tối ưu hóa, chúng tôi có thể xử lý tải - nhưng tôi rất tin tưởng vào phương pháp này là một giải pháp tiết kiệm chi phí lớn trong dài hạn. Nếu bạn biết một số phản hồi bạn có thể phục vụ trước, nếu bạn làm ấm bộ đệm trước luồng yêu cầu, mức tiết kiệm tài nguyên phụ trợ và độ tin cậy sẽ rất cao.
iamyojimbo

Câu trả lời:


9

Tôi đã cố gắng giải quyết một vấn đề tương tự. Người dùng của tôi cần được xác thực cho mọi yêu cầu họ đưa ra. Tôi đã tập trung vào việc xác thực người dùng ít nhất một lần bằng ứng dụng phụ trợ (xác thực mã thông báo JWT), nhưng sau đó, tôi quyết định không nên sử dụng phụ trợ nữa.

Tôi đã chọn để tránh yêu cầu bất kỳ plugin Nginx nào không được bao gồm theo mặc định. Nếu không, bạn có thể kiểm tra kịch bản nginx-jwt hoặc Lua và đây có thể là những giải pháp tuyệt vời.

Xác thực địa chỉ

Cho đến nay tôi đã làm như sau:

  • Đã ủy quyền xác thực cho Nginx bằng cách sử dụng auth_request. Điều này gọi một internalvị trí chuyển yêu cầu đến điểm cuối xác thực mã thông báo phụ trợ của tôi. Điều này một mình không giải quyết vấn đề xử lý một số lượng lớn các xác nhận.

  • Kết quả xác thực mã thông báo được lưu trữ bằng cách sử dụng một lệnh proxy_cache_key "$cookie_token";. Sau khi xác thực mã thông báo thành công, phần phụ trợ sẽ thêm một lệnh Cache-Controlcho Nginx chỉ lưu bộ đệm mã thông báo trong tối đa 5 phút. Tại thời điểm này, bất kỳ mã thông báo xác thực nào được xác thực một lần đều nằm trong bộ đệm, các yêu cầu tiếp theo từ cùng một người dùng / mã thông báo sẽ không chạm vào phụ trợ xác thực nữa!

  • Để bảo vệ ứng dụng phụ trợ của tôi chống lại lũ lụt tiềm năng bởi các mã thông báo không hợp lệ, tôi cũng lưu bộ đệm từ chối xác thực, khi điểm cuối phụ trợ của tôi trả về 401. Những ứng dụng này chỉ được lưu trong bộ nhớ cache trong một thời gian ngắn để tránh có khả năng lấp đầy bộ đệm Nginx với các yêu cầu như vậy.

Tôi đã thêm một vài cải tiến bổ sung như điểm cuối đăng xuất làm mất hiệu lực mã thông báo bằng cách trả lại 401 (cũng được Nginx lưu vào bộ nhớ cache) để nếu người dùng nhấp vào đăng xuất, mã thông báo không thể được sử dụng nữa ngay cả khi chưa hết hạn.

Ngoài ra, bộ đệm Nginx của tôi chứa cho mọi mã thông báo, người dùng được liên kết dưới dạng đối tượng JSON, giúp tôi không tìm nạp nó từ DB nếu tôi cần thông tin này; và cũng tiết kiệm cho tôi khỏi việc giải mã mã thông báo.

Giới thiệu về mã thông báo trọn đời và làm mới mã thông báo

Sau 5 phút, mã thông báo sẽ hết hạn trong bộ đệm, vì vậy phần phụ trợ sẽ được truy vấn lại. Điều này là để đảm bảo rằng bạn có thể làm mất hiệu lực mã thông báo, vì người dùng đăng xuất, vì nó đã bị xâm phạm, v.v. Xác nhận lại định kỳ như vậy, với việc thực hiện đúng trong phần phụ trợ, tránh cho tôi phải sử dụng mã thông báo làm mới.

Các mã thông báo làm mới theo truyền thống sẽ được sử dụng để yêu cầu mã thông báo truy cập mới; chúng sẽ được lưu trữ trong phần phụ trợ của bạn và bạn sẽ xác minh rằng yêu cầu mã thông báo truy cập được thực hiện với mã thông báo làm mới phù hợp với mã thông báo bạn có trong cơ sở dữ liệu cho người dùng cụ thể này. Nếu người dùng đăng xuất hoặc mã thông báo bị xâm phạm, bạn sẽ xóa / làm mất hiệu lực mã thông báo làm mới trong DB của bạn để yêu cầu tiếp theo cho mã thông báo mới sử dụng mã thông báo làm mới không hợp lệ sẽ không thành công.

Nói tóm lại, mã thông báo làm mới thường có giá trị lâu dài và luôn được kiểm tra đối với phụ trợ. Chúng được sử dụng để tạo mã thông báo truy cập có giá trị rất ngắn (một vài phút). Các mã thông báo truy cập này thường đạt đến phụ trợ của bạn nhưng bạn chỉ kiểm tra chữ ký và ngày hết hạn của chúng.

Ở đây trong thiết lập của tôi, chúng tôi đang sử dụng mã thông báo có hiệu lực lâu hơn (có thể là giờ hoặc một ngày), có cùng vai trò và tính năng như cả mã thông báo truy cập và mã thông báo làm mới. Vì chúng tôi có Nginx xác thực và vô hiệu hóa bộ nhớ cache của họ, nên chúng chỉ được xác minh đầy đủ bởi phụ trợ cứ sau 5 phút. Vì vậy, chúng tôi giữ lợi ích của việc sử dụng mã thông báo làm mới (có thể nhanh chóng vô hiệu hóa mã thông báo) mà không cần thêm độ phức tạp. Và xác nhận đơn giản không bao giờ đạt đến phụ trợ của bạn chậm hơn ít nhất 1 bậc so với bộ đệm Nginx, ngay cả khi chỉ được sử dụng để kiểm tra chữ ký và ngày hết hạn.

Với thiết lập này, tôi có thể vô hiệu hóa xác thực trong phần phụ trợ của mình, vì tất cả các yêu cầu đến đều đến auth_requestchỉ thị Nginx trước khi chạm vào nó.

Điều đó không giải quyết được hoàn toàn vấn đề nếu bạn cần thực hiện bất kỳ loại ủy quyền theo tài nguyên nào, nhưng ít nhất bạn đã lưu phần ủy quyền cơ bản. Và bạn thậm chí có thể tránh giải mã mã thông báo hoặc thực hiện tra cứu DB để truy cập dữ liệu mã thông báo vì phản hồi xác thực được lưu trong bộ nhớ cache của Nginx có thể chứa dữ liệu và chuyển nó trở lại vào phụ trợ.

Bây giờ, mối quan tâm lớn nhất của tôi là tôi có thể phá vỡ một cái gì đó rõ ràng liên quan đến bảo mật mà không nhận ra nó. Điều đó đang được nói, bất kỳ mã thông báo nhận được nào vẫn được xác thực ít nhất một lần trước khi được Nginx lưu trữ. Bất kỳ mã thông báo nóng nào cũng sẽ khác nhau vì vậy sẽ không nhấn vào bộ đệm vì khóa bộ đệm cũng sẽ khác.

Ngoài ra, có lẽ đáng nói đến việc xác thực trong thế giới thực sẽ chống lại việc đánh cắp mã thông báo bằng cách tạo (và xác minh) một Nonce bổ sung hoặc thứ gì đó.

Đây là một trích xuất đơn giản hóa cấu hình Nginx của tôi cho ứng dụng của tôi:

# 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;
    }

}

Bây giờ, đây là trích xuất cấu hình cho /authđiểm cuối bên trong , bao gồm ở trên như /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;
}

.

Địa chỉ phục vụ nội dung

Bây giờ xác thực được tách ra khỏi dữ liệu. Vì bạn đã nói nó giống hệt nhau cho mọi người dùng, nên nội dung cũng có thể được Nginx lưu vào bộ nhớ cache (trong ví dụ của tôi, trong content_cachevùng).

Khả năng mở rộng

Kịch bản này hoạt động rất tốt khi cho rằng bạn có một máy chủ Nginx. Trong một kịch bản trong thế giới thực, bạn có thể có tính sẵn sàng cao, có nghĩa là nhiều trường hợp Nginx, cũng có khả năng lưu trữ ứng dụng phụ trợ (Laravel) của bạn. Trong trường hợp đó, mọi yêu cầu mà người dùng của bạn đưa ra có thể được gửi đến bất kỳ máy chủ Nginx nào của bạn và cho đến khi tất cả họ đã lưu trữ mã thông báo cục bộ, họ sẽ tiếp tục truy cập vào phần phụ trợ của bạn để xác minh. Đối với một số lượng nhỏ máy chủ, sử dụng giải pháp này vẫn sẽ mang lại lợi ích lớn.

Tuy nhiên, điều quan trọng cần lưu ý là với nhiều máy chủ Nginx (và do đó lưu trữ), bạn sẽ mất khả năng đăng xuất ở phía máy chủ vì bạn không thể thanh lọc (bằng cách buộc làm mới) bộ nhớ cache mã thông báo trên tất cả chúng, như /auth/logoutlàm trong ví dụ của tôi. Bạn chỉ còn lại với thời lượng bộ nhớ cache mã thông báo 5 triệu sẽ buộc yêu cầu phụ trợ của bạn sớm được truy vấn và sẽ cho Nginx biết rằng yêu cầu bị từ chối. Cách giải quyết một phần là xóa tiêu đề mã thông báo hoặc cookie trên máy khách khi đăng xuất.

Bất kỳ bình luận sẽ rất hoan nghênh và đánh giá cao!


Bạn sẽ nhận được nhiều hơn upvote! Rất hữu ích, cảm ơn!
Gershon Papi

"Tôi đã thêm một vài cải tiến bổ sung như điểm cuối đăng xuất làm mất hiệu lực mã thông báo bằng cách trả lại 401 (cũng được Nginx lưu vào bộ nhớ cache) để nếu người dùng nhấp vào đăng xuất, mã thông báo không thể được sử dụng nữa ngay cả khi chưa hết hạn. " - Thật là thông minh! , nhưng bạn có thực sự đưa vào danh sách đen mã thông báo trong phần phụ trợ của mình không, để trong trường hợp bộ đệm bị hỏng hoặc một cái gì đó, người dùng vẫn không thể đăng nhập bằng mã thông báo cụ thể đó?
gaurav5430

"Tuy nhiên, điều quan trọng cần lưu ý là với nhiều máy chủ Nginx (và do đó lưu trữ), bạn sẽ mất khả năng đăng xuất ở phía máy chủ vì bạn không thể lọc (bằng cách buộc làm mới) bộ nhớ cache của tất cả chúng, like / auth / logout hiện trong ví dụ của tôi. " bạn có thể xây dựng?
gaurav5430
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.