모든 사용자에 대해 인증 된 요청 캐싱


9

동일한 콘텐츠를 요청하려면 승인이 필요한 동시 사용자의 매우 큰 충동을 처리 해야하는 웹 앱을 개발 중입니다. 현재 상태에서는 32 코어 AWS 인스턴스까지 완전히 무너지고 있습니다.

(우리는 Nginx를 리버스 프록시로 사용하고 있습니다)

최악의 경우 JWT를 디코딩하여 사용자가 인증되었는지 확인해야하므로 응답을 간단히 캐시 할 수 없습니다. 이것은 대부분 동의 할 것이다 Laravel 4, 점화를 우리에게 필요하다 느린 PHP-FPM과 OpCache 활성화도 함께. 이것은 대부분 무거운 부트 스트랩 단계 때문입니다.

"이것이 문제가된다는 것을 알고 있다면 왜 PHP와 Laravel을 처음 사용하셨습니까?" 하지만 그 결정을 다시하기에는 너무 늦었습니다!

가능한 해결책

한 가지 해결책은 Laravel에서 Auth 모듈을 JWT를 디코딩하고 사용자 인증 여부를 결정하는 책임이있는 경량 외부 모듈 (C와 같이 빠른 것으로 작성 됨)로 추출하는 것입니다.

요청의 흐름은 다음과 같습니다.

  1. 캐시 적중 여부 확인 (정상적으로 PHP에 전달되지 않는 경우)
  2. 토큰 디코딩
  3. 유효한지 확인하십시오
  4. 유효한 경우 캐시에서 제공
  5. 유효하지 않은 경우 Nginx에 알리면 Nginx는 요청을 PHP에 전달하여 정상적으로 처리합니다.

이를 통해 단일 사용자에게이 요청을 제공 한 후 가벼운 모듈에 손을 뻗어 JWT 및이 유형의 인증과 함께 제공되는 다른 경고를 해결해야합니다.

나는이 코드를 Nginx HTTP 확장 모듈로 직접 작성하려고 생각했습니다.

우려 사항

내 관심사는 내가 한 일을 본 적이 없으며 더 좋은 방법이 있는지 궁금해한다는 것입니다.

또한 두 번째로 사용자 특정 컨텐츠를 페이지에 추가하면이 메소드가 완전히 종료됩니다.

Nginx에서 직접 사용할 수있는 더 간단한 솔루션이 있습니까? 아니면 바니시와 같이 더 전문적인 것을 사용해야합니까?

내 질문 :

위의 해결책이 의미가 있습니까?

이것은 일반적으로 어떻게 접근합니까?

비슷하거나 더 나은 성능을 얻을 수있는 더 좋은 방법이 있습니까?


비슷한 문제로 어려움을 겪고 있습니다. 몇 가지 아이디어 a) Nginx auth_request가 인증 마이크로 서비스로 넘어 가서 Nginx 모듈을 개발할 필요성을 완화 할 수 있습니다. b) 또는, 마이크로 서비스는 인증 된 사용자를 공개 URL, 캐시 가능 및 추측 불가능한 임시 URL로 리디렉션 할 수 있지만 제한된 기간 (캐시 기간) 동안 유효하도록 PHP 백엔드에서 유효성을 검증 할 수 있습니다. 이렇게하면 임시 URL이 신뢰할 수없는 사용자에게 유출되면 OAuth 베어러 토큰과 같이 제한된 기간 동안 콘텐츠에 액세스 할 수 있습니다.
제임스

이것에 대한 해결책을 생각해 냈습니까? 나는 같은 일을 직면하고있어
timbroder

우리는 최적화 된 백엔드 노드의 큰 클러스터를 가짐으로써 부하를 처리 할 수 ​​있었지만이 접근법이 장기적으로 큰 비용 절감 솔루션이라는 확신을 가지고 있습니다. 미리 제공 할 수있는 응답 중 일부를 알고있는 경우, 요청이 유입되기 전에 캐시를 예열하면 백엔드 리소스 절약 및 안정성이 매우 높아집니다.
iamyojimbo

답변:


9

비슷한 문제를 해결하려고 노력했습니다. 내 사용자는 모든 요청에 ​​대해 인증을 받아야합니다. 백엔드 앱 (JWT 토큰 확인)으로 사용자를 한 번 이상 인증하는 데 중점을 두었지만 더 이상 백엔드가 필요하지 않다고 결정했습니다.

기본적으로 포함되어 있지 않은 Nginx 플러그인이 필요하지 않도록 선택했습니다. 그렇지 않으면 nginx-jwt 또는 Lua 스크립팅을 확인할 수 있으며 이는 훌륭한 솔루션 일 것입니다.

주소 인증

지금까지 나는 다음을 수행했다.

  • 를 사용하여 인증을 Nginx에 위임했습니다 auth_request. internal백엔드 토큰 유효성 검사 엔드 포인트로 요청을 전달 하는 위치를 호출 합니다. 이것만으로는 많은 수의 검증을 처리하는 문제를 해결하지 못합니다.

  • 토큰 유효성 검사 결과는 proxy_cache_key "$cookie_token";지시문을 사용하여 캐시됩니다 . 토큰 유효성 검사에 성공하면 백엔드는 Cache-ControlNginx에 최대 5 분 동안 만 토큰을 캐시하도록 지시 하는 지시문을 추가합니다 . 이 시점에서 한 번 확인 된 인증 토큰은 캐시에 있으며 동일한 사용자 / 토큰의 후속 요청은 더 이상 인증 백엔드에 닿지 않습니다!

  • 백엔드 엔드 포인트가 401을 반환하면 백엔드 앱이 유효하지 않은 토큰에 의한 잠재적 홍수로부터 보호하기 위해 백엔드 엔드 포인트가 401을 반환 할 때 거부 된 유효성 검사도 캐시합니다. 이러한 요청은 Nginx 캐시에 이러한 요청이 채워지지 않도록 짧은 기간 동안 만 캐시됩니다.

401 (Nginx에 의해 캐시 됨)을 반환하여 토큰을 무효화하는 로그 아웃 엔드 포인트와 같은 몇 가지 추가 기능을 추가하여 사용자가 로그 아웃을 클릭하면 만료되지 않아도 더 이상 토큰을 사용할 수 없습니다.

또한 내 Nginx 캐시에는 모든 토큰에 대해 연결된 사용자를 JSON 객체로 포함 하므로이 정보가 필요한 경우 DB에서 토큰을 가져 오는 것을 방지합니다. 또한 토큰을 해독하지 않아도됩니다.

토큰 수명 및 새로 고침 토큰 정보

5 분 후에 토큰이 캐시에서 만료되어 백엔드가 다시 쿼리됩니다. 이는 사용자가 로그 아웃했거나 손상 되었기 때문에 토큰을 무효화 할 수 있도록하기위한 것입니다. 백엔드에서 올바르게 구현 된주기적인 유효성 재확인을 통해 새로 고침 토큰을 사용할 필요가 없습니다.

전통적으로 새로 고침 토큰은 새로운 액세스 토큰을 요청하는 데 사용됩니다. 이들은 백엔드에 저장되며이 특정 사용자에 대해 데이터베이스에있는 것과 일치하는 새로 고침 토큰으로 액세스 토큰 요청이 이루어 졌는지 확인합니다. 사용자가 로그 아웃하거나 토큰이 손상된 경우 무효화 된 새로 고침 토큰을 사용하여 새 토큰에 대한 다음 요청이 실패하도록 DB에서 새로 고침 토큰을 삭제 / 무효화합니다.

즉, 새로 고침 토큰은 일반적으로 유효 기간이 길고 항상 백엔드와 비교하여 확인됩니다. 유효성이 매우 짧은 (몇 분) 액세스 토큰을 생성하는 데 사용됩니다. 이러한 액세스 토큰은 일반적으로 백엔드에 도달하지만 서명 및 만료 날짜 만 확인합니다.

여기서는 액세스 토큰 및 새로 고침 토큰과 동일한 역할 및 기능을 가진 유효 기간이 더 긴 토큰 (수 시간 또는 하루가 될 수 있음)을 사용합니다. Nginx에 의해 캐시 된 유효성 검사 및 무효화가 있기 때문에 5 분마다 한 번씩 백엔드에 의해 완전히 검증됩니다. 따라서 복잡성을 추가하지 않고도 새로 고침 토큰 (토큰을 빠르게 무효화 할 수 있음)을 사용할 수 있다는 이점이 있습니다. 또한 서명 및 만료 날짜 확인에만 사용하더라도 간단한 유효성 검사는 Nginx 캐시보다 적어도 1 배 느린 백엔드에 도달하지 않습니다.

이 설정을 사용하면 들어오는 모든 요청이 auth_requestNginx 지시어에 닿기 전에 도달하므로 백엔드에서 인증을 비활성화 할 수 있습니다.

리소스 별 인증을 수행해야하는 경우 문제를 완전히 해결하지는 못하지만 최소한 기본 인증 부분을 저장했습니다. 또한 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에 알립니다. 부분 해결 방법은 로그 아웃 할 때 클라이언트에서 토큰 헤더 또는 쿠키를 삭제하는 것입니다.

모든 의견은 매우 환영하고 감사합니다!


당신은 더 많은 공의를 받고 있어야합니다! 매우 도움이되었습니다. 감사합니다!
Gershon Papi 2016 년

"Nginx에 의해 캐시되는 401을 반환하여 토큰을 무효화하는 로그 아웃 엔드 포인트와 같은 몇 가지 추가 기능을 추가하여 사용자가 로그 아웃을 클릭하면 토큰이 만료되지 않아도 더 이상 사용할 수 없습니다. " -영리 해요! 하지만 실제로 백엔드에서 토큰을 블랙리스트에 추가하여 캐시가 다운되거나 무언가가 발생하더라도 사용자가 해당 특정 토큰으로 여전히 로그인 할 수 없도록합니까?
gaurav5430

"하지만 여러 개의 Nginx 서버 (및 캐시)를 사용하면 모든 서버에서 토큰 캐시를 제거 (새로 고침) 할 수 없기 때문에 서버 측에서 로그 아웃 할 수 없게됩니다. 예를 들어 / auth / logout과 같습니다. " 정교하게 할 수 있습니까?
gaurav5430
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.