비슷한 문제를 해결하려고 노력했습니다. 내 사용자는 모든 요청에 대해 인증을 받아야합니다. 백엔드 앱 (JWT 토큰 확인)으로 사용자를 한 번 이상 인증하는 데 중점을 두었지만 더 이상 백엔드가 필요하지 않다고 결정했습니다.
기본적으로 포함되어 있지 않은 Nginx 플러그인이 필요하지 않도록 선택했습니다. 그렇지 않으면 nginx-jwt 또는 Lua 스크립팅을 확인할 수 있으며 이는 훌륭한 솔루션 일 것입니다.
주소 인증
지금까지 나는 다음을 수행했다.
를 사용하여 인증을 Nginx에 위임했습니다 auth_request
. internal
백엔드 토큰 유효성 검사 엔드 포인트로 요청을 전달 하는 위치를 호출 합니다. 이것만으로는 많은 수의 검증을 처리하는 문제를 해결하지 못합니다.
토큰 유효성 검사 결과는 proxy_cache_key "$cookie_token";
지시문을 사용하여 캐시됩니다 . 토큰 유효성 검사에 성공하면 백엔드는 Cache-Control
Nginx에 최대 5 분 동안 만 토큰을 캐시하도록 지시 하는 지시문을 추가합니다 . 이 시점에서 한 번 확인 된 인증 토큰은 캐시에 있으며 동일한 사용자 / 토큰의 후속 요청은 더 이상 인증 백엔드에 닿지 않습니다!
백엔드 엔드 포인트가 401을 반환하면 백엔드 앱이 유효하지 않은 토큰에 의한 잠재적 홍수로부터 보호하기 위해 백엔드 엔드 포인트가 401을 반환 할 때 거부 된 유효성 검사도 캐시합니다. 이러한 요청은 Nginx 캐시에 이러한 요청이 채워지지 않도록 짧은 기간 동안 만 캐시됩니다.
401 (Nginx에 의해 캐시 됨)을 반환하여 토큰을 무효화하는 로그 아웃 엔드 포인트와 같은 몇 가지 추가 기능을 추가하여 사용자가 로그 아웃을 클릭하면 만료되지 않아도 더 이상 토큰을 사용할 수 없습니다.
또한 내 Nginx 캐시에는 모든 토큰에 대해 연결된 사용자를 JSON 객체로 포함 하므로이 정보가 필요한 경우 DB에서 토큰을 가져 오는 것을 방지합니다. 또한 토큰을 해독하지 않아도됩니다.
토큰 수명 및 새로 고침 토큰 정보
5 분 후에 토큰이 캐시에서 만료되어 백엔드가 다시 쿼리됩니다. 이는 사용자가 로그 아웃했거나 손상 되었기 때문에 토큰을 무효화 할 수 있도록하기위한 것입니다. 백엔드에서 올바르게 구현 된주기적인 유효성 재확인을 통해 새로 고침 토큰을 사용할 필요가 없습니다.
전통적으로 새로 고침 토큰은 새로운 액세스 토큰을 요청하는 데 사용됩니다. 이들은 백엔드에 저장되며이 특정 사용자에 대해 데이터베이스에있는 것과 일치하는 새로 고침 토큰으로 액세스 토큰 요청이 이루어 졌는지 확인합니다. 사용자가 로그 아웃하거나 토큰이 손상된 경우 무효화 된 새로 고침 토큰을 사용하여 새 토큰에 대한 다음 요청이 실패하도록 DB에서 새로 고침 토큰을 삭제 / 무효화합니다.
즉, 새로 고침 토큰은 일반적으로 유효 기간이 길고 항상 백엔드와 비교하여 확인됩니다. 유효성이 매우 짧은 (몇 분) 액세스 토큰을 생성하는 데 사용됩니다. 이러한 액세스 토큰은 일반적으로 백엔드에 도달하지만 서명 및 만료 날짜 만 확인합니다.
여기서는 액세스 토큰 및 새로 고침 토큰과 동일한 역할 및 기능을 가진 유효 기간이 더 긴 토큰 (수 시간 또는 하루가 될 수 있음)을 사용합니다. Nginx에 의해 캐시 된 유효성 검사 및 무효화가 있기 때문에 5 분마다 한 번씩 백엔드에 의해 완전히 검증됩니다. 따라서 복잡성을 추가하지 않고도 새로 고침 토큰 (토큰을 빠르게 무효화 할 수 있음)을 사용할 수 있다는 이점이 있습니다. 또한 서명 및 만료 날짜 확인에만 사용하더라도 간단한 유효성 검사는 Nginx 캐시보다 적어도 1 배 느린 백엔드에 도달하지 않습니다.
이 설정을 사용하면 들어오는 모든 요청이 auth_request
Nginx 지시어에 닿기 전에 도달하므로 백엔드에서 인증을 비활성화 할 수 있습니다.
리소스 별 인증을 수행해야하는 경우 문제를 완전히 해결하지는 못하지만 최소한 기본 인증 부분을 저장했습니다. 또한 Nginx 캐시 인증 응답에 데이터가 포함되어 백엔드로 다시 전달 될 수 있으므로 토큰의 암호 해독을 피하거나 토큰 조회에 액세스하기 위해 DB 조회를 수행 할 수도 있습니다.
이제 가장 큰 걱정은 보안과 관련하여 명백한 것을 깨뜨리지 않고 깨뜨릴 수 있다는 것입니다. 즉, 수신 된 토큰은 여전히 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
내 예에서 않습니다. 백엔드가 곧 쿼리되도록하는 5 백만 개의 토큰 캐시 기간 만 남게되고 요청이 거부되었음을 Nginx에 알립니다. 부분 해결 방법은 로그 아웃 할 때 클라이언트에서 토큰 헤더 또는 쿠키를 삭제하는 것입니다.
모든 의견은 매우 환영하고 감사합니다!