Memorizzazione nella cache delle richieste autenticate per tutti gli utenti

5

Sto lavorando su un'app Web che deve gestire impulsi molto ampi di utenti simultanei, che devono essere autorizzati, per richiedere contenuti identici. Allo stato attuale, è totalmente paralizzante persino per un'istanza AWS a 32 core.

(Si noti che stiamo usando Nginx come proxy inverso)

La risposta non può essere semplicemente memorizzata nella cache poiché, nel peggiore dei casi, è necessario verificare se l'utente è autenticato decodificando il proprio JWT. Questo ci impone di attivare Laravel 4, che la maggior parte sarebbe d'accordo, è lento , anche con PHP-FPM e OpCache abilitati. Ciò è dovuto principalmente alla pesante fase di bootstrap.

Si potrebbe fare la domanda "Perché hai usato PHP e Laravel se avessi saputo che questo sarebbe stato un problema?" - ma è troppo tardi per tornare su quella decisione!

Soluzione possibile

Una soluzione che è stata avanzata è quella di estrarre il modulo Auth da Laravel in un modulo esterno leggero (scritto in qualcosa di veloce come C) la cui responsabilità è decodificare il JWT e decidere se l'utente è autenticato.

Il flusso di una richiesta sarebbe:

  1. Verifica se la cache ha colpito (se non passi a PHP normalmente)
  2. Decodifica il token
  3. Verifica se è valido
  4. Se valido , viene pubblicato dalla cache
  5. Se non valido , comunica a Nginx, quindi Nginx passerà la richiesta a PHP per gestire normalmente.

Questo ci permetterà di non colpire PHP una volta che abbiamo servito questa richiesta a un singolo utente e invece di raggiungere un modulo leggero per scherzare con le JWT di decodifica e qualsiasi altro avvertimento che viene fornito con questo tipo di autenticazione.

Stavo addirittura pensando di scrivere questo codice direttamente come modulo di estensione HTTP Nginx.

Le preoccupazioni

La mia preoccupazione è che non ho mai visto questo fatto prima e mi chiedevo se c'è un modo migliore.

Inoltre, nel secondo si aggiunge qualsiasi contenuto specifico dell'utente alla pagina, si uccide totalmente questo metodo.

Esiste un'altra soluzione più semplice disponibile direttamente in Nginx? O dovremmo usare qualcosa di più specializzato come Varnish?

Le mie domande:

La soluzione di cui sopra ha senso?

Come si avvicina normalmente?

C'è un modo migliore per ottenere un guadagno di prestazioni simile o migliore?

    
posta iamyojimbo 10.10.2015 - 15:03
fonte

1 risposta

3

Ho cercato di risolvere un problema simile. I miei utenti devono essere autenticati per ogni richiesta che fanno. Mi sono concentrato sull'ottenere l'autenticazione degli utenti almeno una volta dall'app backend (convalida del token JWT), ma dopo ho deciso che non avrei più bisogno del back-end.

Ho scelto di evitare di richiedere qualsiasi plug-in Nginx che non sia incluso di default. Altrimenti puoi controllare nginx-jwt o Lua scripting e queste sarebbero probabilmente ottime soluzioni.

Indirizzamento dell'autenticazione

Finora ho fatto quanto segue:

  • Delegata l'autenticazione a Nginx usando auth_request . Ciò chiama un'ubicazione internal che passa la richiesta al mio endpoint di convalida del token di back-end. Questo da solo non risolve il problema della gestione di un numero elevato di convalide.

  • Il risultato della convalida del token viene memorizzato nella cache utilizzando una direttiva proxy_cache_key "$cookie_token"; . In seguito alla convalida del token, il backend aggiunge una direttiva Cache-Control che indica a Nginx di memorizzare solo il token nella cache fino alla data di scadenza. A questo punto, qualsiasi token di autenticazione convalidato una volta è nella cache, le richieste successive dallo stesso utente non toccano più il back-end di autenticazione!

  • Per proteggere la mia app di back-end da potenziali inondazioni da token non validi, memorizzo anche le convalide rifiutate, quando il mio endpoint backend restituisce 401. Questi sono solo memorizzati nella cache per una breve durata per evitare potenzialmente di riempire la cache di Nginx con tali richieste

Ho aggiunto un paio di ulteriori miglioramenti come un endpoint di logout che invalida un token restituendo 401 (che è anche memorizzato nella cache da Nginx) in modo che se l'utente fa clic su Esci, il token non può più essere utilizzato anche se non lo è scaduto.

Inoltre, la mia cache Nginx contiene per ogni token, l'utente associato come un oggetto JSON, che mi salva dal recuperarlo dal DB se ho bisogno di queste informazioni; e inoltre mi salva dalla decrittografia del token.

Con questa configurazione, potrei disabilitare l'autenticazione nel mio back-end, poiché tutte le richieste in arrivo raggiungono la direttiva auth_request Nginx prima di toccarla.

Questo non risolve completamente il problema se è necessario eseguire qualsiasi tipo di autorizzazione per risorsa, ma almeno hai salvato la parte di autorizzazione di base. Inoltre, puoi evitare di decodificare il token o effettuare una ricerca DB per accedere ai dati dei token poiché la risposta dell'autenticazione memorizzata nella cache di Nginx può contenere dati e passarli indietro al back-end.

Ora, la mia più grande preoccupazione è che potrei rompere qualcosa di ovvio relativo alla sicurezza senza rendermene conto. Detto questo, ogni token ricevuto viene comunque convalidato almeno una volta prima di essere memorizzato nella cache da Nginx. Qualsiasi token temperato sarebbe diverso, quindi non colpirebbe la cache dato che anche la chiave della cache sarebbe diversa.

Inoltre, forse vale la pena ricordare che un'autenticazione del mondo reale avrebbe combattuto contro il furto di token generando (e verificando) un Nonce o qualcosa di simile.

Ecco un estratto semplificato della mia configurazione di Nginx per la mia app:

# 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=30m 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;
    }

}

Ora, ecco l'estratto di configurazione per l'endpoint auth interno, incluso sopra come /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;

    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 401 response for the token for longer than
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;
}

.

Indirizzamento del contenuto che serve

Ora l'autenticazione è separata dai dati. Poiché hai detto che era identico per ogni utente, il contenuto stesso può anche essere memorizzato nella cache da Nginx (nel mio esempio, nella zona content_cache ).

Qualsiasi commento sarebbe molto apprezzato e apprezzato!

    
risposta data 09.12.2017 - 03:56
fonte

Leggi altre domande sui tag