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 internal
vị 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-Control
cho 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_request
chỉ 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_cache
vù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/logout
là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!