Token Web JSON non validi


421

Per un nuovo progetto node.js a cui sto lavorando, sto pensando di passare da un approccio di sessione basato su cookie (con questo, voglio dire, memorizzare un ID in un archivio di valori-chiave contenente sessioni utente nel browser di un utente) a un approccio di sessione basato su token (nessun archivio valori-chiave) utilizzando i token Web JSON (jwt).

Il progetto è un gioco che utilizza socket.io: avere una sessione basata su token sarebbe utile in un tale scenario in cui ci saranno più canali di comunicazione in una singola sessione (web e socket.io)

Come si può fornire l'invalidazione token / sessione dal server usando l'approccio jwt?

Volevo anche capire quali trappole / attacchi comuni (o non comuni) avrei dovuto cercare con questo tipo di paradigma. Ad esempio, se questo paradigma è vulnerabile agli stessi / diversi tipi di attacchi dell'approccio basato sull'archivio sessioni / cookie.

Quindi, supponiamo che io abbia il seguente (adattato da questo e questo ):

Login negozio sessione:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

Accesso basato su token:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

-

Un logout (o non valido) per l'approccio Session Store richiederebbe un aggiornamento al database KeyValueStore con il token specificato.

Sembra che un tale meccanismo non esisterebbe nell'approccio basato su token poiché il token stesso conterrebbe le informazioni che normalmente esisterebbero nell'archivio valori-chiave.


1
Se stai usando il pacchetto 'express-jwt', puoi dare un'occhiata isRevokedall'opzione o provare a replicare la stessa funzionalità. github.com/auth0/express-jwt#revoked-tokens
Signus

1
Prendere in considerazione l'utilizzo di un tempo di scadenza breve sul token di accesso e l'utilizzo di un token di aggiornamento, con una scadenza di lunga durata, per consentire il controllo dello stato di accesso dell'utente in un database (lista nera). auth0.com/blog/…
Rohmer,

un'altra opzione sarebbe quella di collegare l'indirizzo IP nel payload durante la generazione di token jwt e il controllo dell'IP memorizzato rispetto alla richiesta in entrata per lo stesso indirizzo IP. es: req.connection.remoteAddress in nodeJs. Ci sono provider ISP che non emettono IP statici per cliente, penso che questo non sarà un problema a meno che un client non si riconnetta a Internet.
Gihan Sandaru,

Risposte:


392

Anch'io ho fatto ricerche su questa domanda e, sebbene nessuna delle idee seguenti sia una soluzione completa, potrebbe aiutare gli altri a escludere idee o fornirne altre.

1) Rimuovere semplicemente il token dal client

Ovviamente questo non fa nulla per la sicurezza lato server, ma blocca un attaccante rimuovendo il token dall'esistenza (cioè, avrebbero dovuto rubare il token prima del logout).

2) Crea una lista nera di token

È possibile archiviare i token non validi fino alla data di scadenza iniziale e confrontarli con le richieste in arrivo. Questo sembra negare il motivo per andare completamente basato sul token, in primo luogo però, poiché dovresti toccare il database per ogni richiesta. Tuttavia, le dimensioni di archiviazione sarebbero probabilmente inferiori, poiché sarebbe sufficiente archiviare i token che si trovavano tra il logout e il tempo di scadenza (questa è una sensazione viscerale ed è sicuramente dipendente dal contesto).

3) Basta mantenere brevi i tempi di scadenza del token e ruotarli spesso

Se si mantengono i tempi di scadenza del token a intervalli sufficientemente brevi e si fa in modo che il client in esecuzione tenga traccia e richieda gli aggiornamenti quando necessario, il numero 1 funzionerebbe efficacemente come sistema di logout completo. Il problema con questo metodo è che rende impossibile mantenere l'utente connesso tra le chiusure del codice client (a seconda della durata dell'intervallo di scadenza).

Piani di emergenza

Se si è mai verificata un'emergenza o un token utente è stato compromesso, una cosa che puoi fare è consentire all'utente di modificare un ID di ricerca utente sottostante con le proprie credenziali di accesso. Ciò renderebbe non validi tutti i token associati, in quanto l'utente associato non sarà più in grado di essere trovato.

Volevo anche notare che è una buona idea includere l'ultima data di accesso con il token, in modo da poter applicare un nuovo accesso dopo un periodo di tempo distante.

In termini di somiglianze / differenze riguardo agli attacchi che usano token, questo post affronta la domanda: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -Vs-token.markdown


3
Approccio eccellente Il mio istinto sarebbe quello di fare una combinazione di tutti e 3, e / o, richiedere un nuovo token dopo ogni "n" richiesta (al contrario di un timer). Stiamo usando redis per l'archiviazione di oggetti in memoria, e potremmo facilmente usarlo per il caso n. 2, e quindi la latenza diminuirà.
Aaron Wagner,

2
Questo post horror di programmazione offre alcuni consigli: mantieni brevi i cookie (o i token) della sessione ma rendili invisibili all'utente - che sembra essere in linea con il n. 3. Il mio istinto (forse perché è più tradizionale) è solo che il token (o un hash di esso) funga da chiave nel database delle sessioni in elenco bianco (simile al n. 2)
funseiki,

8
L'articolo è ben scritto ed è una versione elaborata di cui 2)sopra. Mentre funziona bene, personalmente non vedo molta differenza rispetto ai tradizionali store di sessione. Immagino che il requisito di archiviazione sia inferiore, ma è comunque necessario un database. Il più grande appello di JWT per me era di non usare affatto un database per le sessioni.
Matt Way,

211
Un approccio comune per invalidare i token quando un utente cambia la propria password è firmare il token con un hash della propria password. Pertanto, se la password cambia, tutti i token precedenti non vengono verificati automaticamente. È possibile estenderlo al logout includendo un tempo di ultimo logout nel record dell'utente e utilizzando una combinazione di ultimo tempo di logout e hash della password per firmare il token. Ciò richiede una ricerca DB ogni volta che è necessario verificare la firma del token, ma presumibilmente si cerca comunque l'utente.
Travis Terry,

4
Una lista nera può essere resa efficiente mantenendola in memoria, in modo che il DB debba essere colpito solo per registrare le invalidazioni e rimuovere le invalidazioni scadute e leggere solo all'avvio del server. In un'architettura di bilanciamento del carico, la blacklist in memoria può eseguire il polling del DB a brevi intervalli, come 10 secondi, limitando l'esposizione di token non validi. Questi approcci consentono al server di continuare ad autenticare le richieste senza accessi al DB per richiesta.
Joe Lapp,

86

Le idee pubblicate sopra sono buone, ma un modo molto semplice e facile per invalidare tutti i JWT esistenti è semplicemente quello di cambiare il segreto.

Se il tuo server crea il JWT, lo firma con un segreto (JWS), quindi lo invia al client, semplicemente cambiando il segreto invalidi tutti i token esistenti e richiedi a tutti gli utenti di ottenere un nuovo token per l'autenticazione poiché il loro vecchio token diventa improvvisamente non valido secondo al server.

Non richiede alcuna modifica al contenuto effettivo del token (o all'ID di ricerca).

Chiaramente questo funziona solo per un caso di emergenza quando si desidera che tutti i token esistenti scadano, per scadenza del token è richiesta una delle soluzioni sopra (come tempo di scadenza del token breve o invalidare una chiave memorizzata all'interno del token).


9
Penso che questo approccio non sia l'ideale. Mentre funziona ed è certamente semplice, immagina un caso in cui stai usando una chiave pubblica: non vorrai andare e ricreare quella chiave ogni volta che vuoi invalidare un singolo token.
Signus,

1
@KijanaWoodard, una coppia di chiavi pubblica / privata può essere utilizzata per convalidare la firma come efficacemente il segreto dell'algoritmo RS256. Nell'esempio mostrato qui menziona la modifica del segreto per invalidare un JWT. Questo può essere fatto a) introducendo un falso pubkey che non corrisponde alla firma oppure b) generando un nuovo pubkey. In quella situazione, è meno che ideale.
Signus,

1
@Signus - gotcha. non usare la chiave pubblica come segreto, ma altri potrebbero fare affidamento sulla chiave pubblica per verificare la firma.
Kijana Woodard,

8
Questa è una pessima soluzione. Il motivo principale per utilizzare JWT è l'apolidia e le scale. L'uso di un segreto dinamico introduce uno stato. Se il servizio è raggruppato su più nodi, è necessario sincronizzare il segreto ogni volta che viene emesso un nuovo token. Dovresti archiviare i segreti in un database o altro servizio esterno, che reinventerebbe l'autenticazione basata sui cookie
Tuomas Toivonen,

5
@TuomasToivonen, ma devi firmare un JWT con un segreto ed essere in grado di verificare il JWT con lo stesso segreto. Quindi è necessario archiviare il segreto sulle risorse protette. Se il segreto è compromesso, è necessario modificarlo e distribuire tale modifica a ciascuno dei nodi. I provider di hosting con clustering / ridimensionamento in genere consentono di archiviare segreti nel loro servizio per rendere la distribuzione di questi segreti semplice e affidabile.
Rohmer,

67

Questo è principalmente un lungo commento a supporto e sulla base della risposta di @mattway

Dato:

Alcune delle altre soluzioni proposte in questa pagina raccomandano di colpire l'archivio dati su ogni richiesta. Se si colpisce l'archivio dati principale per convalidare ogni richiesta di autenticazione, allora vedo meno motivi per utilizzare JWT invece di altri meccanismi di autenticazione token stabiliti. Essenzialmente, hai reso JWT stateful, anziché stateless se vai ogni volta nel datastore.

(Se il tuo sito riceve un volume elevato di richieste non autorizzate, JWT le negherebbe senza colpire l'archivio dati, il che è utile. Probabilmente ci sono altri casi d'uso come quello.)

Dato:

L'autenticazione JWT veramente senza stato non può essere raggiunta per un'app Web tipica del mondo reale poiché JWT senza stato non ha un modo per fornire supporto immediato e sicuro per i seguenti importanti casi d'uso:

L'account dell'utente viene eliminato / bloccato / sospeso.

La password dell'utente è stata modificata.

I ruoli o le autorizzazioni dell'utente vengono modificati.

L'utente è disconnesso dall'amministratore.

Qualsiasi altro dato critico dell'applicazione nel token JWT viene modificato dall'amministratore del sito.

In questi casi non è possibile attendere la scadenza del token. L'invalidazione del token deve avvenire immediatamente. Inoltre, non puoi fidarti del client di non conservare e utilizzare una copia del vecchio token, sia con intenzioni dannose o meno.

Pertanto: penso che la risposta di @ matt-way, # 2 TokenBlackList, sarebbe il modo più efficiente per aggiungere lo stato richiesto all'autenticazione basata su JWT.

Hai una lista nera che contiene questi token fino a quando non viene raggiunta la data di scadenza. L'elenco dei token sarà piuttosto piccolo rispetto al numero totale di utenti, poiché deve solo conservare i token nella lista nera fino alla loro scadenza. Implementerei inserendo token non validi in redis, memcached o un altro archivio dati in memoria che supporta l'impostazione di un tempo di scadenza su una chiave.

Devi ancora effettuare una chiamata al tuo db in memoria per ogni richiesta di autenticazione che passa l'autenticazione JWT iniziale, ma non devi archiviare le chiavi per l'intero set di utenti. (Che può essere o meno un grosso problema per un determinato sito.)


15
Non sono d'accordo con la tua risposta. Colpire un database non rende nulla di stato; lo stato di memorizzazione sul back-end lo fa. JWT non è stato creato in modo da non dover colpire il database su ogni richiesta. Ogni applicazione principale che utilizza JWT è supportata da un database. JWT risolve un problema completamente diverso. en.wikipedia.org/wiki/Stateless_protocol
Julian

6
@Julian puoi approfondire un po 'questo? Quale problema risolve davvero JWT?
zero01alpha,

8
@ zero01alpha Autenticazione: questo è lo scenario più comune per l'utilizzo di JWT. Una volta che l'utente ha effettuato l'accesso, ogni richiesta successiva includerà JWT, consentendo all'utente di accedere a route, servizi e risorse consentiti con quel token. Scambio di informazioni: i token Web JSON sono un buon modo per trasmettere in modo sicuro informazioni tra le parti. Poiché i JWT possono essere firmati, puoi essere sicuro che i mittenti sono chi dicono di essere. Vedi jwt.io/introduction
Julian,

7
@Julian Non sono d'accordo con il tuo disaccordo :) JWT risolve il problema (per i servizi) di avere la necessità di accedere a un'entità centralizzata che fornisce informazioni di autorizzazione per un determinato cliente. Quindi, invece del servizio A e del servizio B, è necessario accedere ad alcune risorse per scoprire se il client X ha o non ha le autorizzazioni per fare qualcosa, il servizio A e B ricevono un token da X che dimostra le sue autorizzazioni (più comunemente emesse da un terzo festa). Comunque, JWT è uno strumento che aiuta a evitare uno stato condiviso tra i servizi in un sistema, specialmente quando sono controllati da più di un fornitore di servizi.
LIvanov,

1
Anche da jwt.io/introduction If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
giantas il

43

Terrei un registro del numero di versione jwt sul modello utente. I nuovi token jwt imposteranno la loro versione su questo.

Quando convalidi il jwt, controlla semplicemente che abbia un numero di versione uguale alla versione jwt corrente dell'utente.

Ogni volta che si desidera invalidare i vecchi jwts, è sufficiente aumentare il numero di versione jwt degli utenti.


15
Questa è un'idea interessante, l'unica cosa è dove archiviare la versione, come parte dello scopo dei token è che è apolide e non ha bisogno di usare il database. Una versione hard coded renderebbe difficile eseguire il bump e un numero di versione in un database annullerebbe alcuni dei vantaggi dell'utilizzo dei token.
Stephen Smith,

13
Presumibilmente stai già memorizzando un ID utente nel token e quindi eseguendo una query sul database per verificare che l'utente esista / sia autorizzato ad accedere all'endpoint API. Quindi non stai eseguendo ulteriori query db confrontando il numero di versione del token jwt con quello dell'utente.
DaftMonk,

5
Non dovrei dirlo presumibilmente, perché ci sono molte situazioni in cui potresti usare token con validazioni che non toccano affatto il database. Ma penso che in questo caso sia difficile da evitare.
DaftMonk,

11
Cosa succede se l'utente accede da più dispositivi? Un token deve essere usato su tutti o dovrebbe invalidare tutti i precedenti?
meeDamian,

10
Sono d'accordo con @SergioCorrea Questo renderebbe JWT quasi altrettanto di qualsiasi altro meccanismo di autenticazione token.
Ed J

40

Non l'ho ancora provato, e utilizza molte informazioni basate su alcune delle altre risposte. La complessità qui è evitare una chiamata nell'archivio dati lato server per richiesta di informazioni sull'utente. La maggior parte delle altre soluzioni richiede una ricerca db per richiesta in un archivio sessioni utente. Questo va bene in alcuni scenari, ma è stato creato nel tentativo di evitare tali chiamate e rendere qualsiasi stato del lato server richiesto molto piccolo. Si finirà per ricreare una sessione lato server, per quanto piccola per fornire tutte le funzionalità di invalidazione forzata. Ma se vuoi farlo, ecco l'essenza:

obiettivi:

  • Mitigare l'uso di un archivio dati (senza stato).
  • Possibilità di forzare il logout di tutti gli utenti.
  • Possibilità di forzare il logout di qualsiasi individuo in qualsiasi momento.
  • Possibilità di reinserire la password dopo un certo periodo di tempo.
  • Capacità di lavorare con più clienti.
  • Possibilità di forzare un nuovo accesso quando un utente fa clic sul logout da un determinato client. (Per impedire a qualcuno di "cancellare" un token client dopo che l'utente si allontana, vedere i commenti per ulteriori informazioni)

La soluzione:

  • Utilizzare token di accesso di breve durata (<5 m) associati a un token di aggiornamento archiviato client di lunga durata (poche ore) .
  • Ogni richiesta controlla la data di scadenza dell'autenticazione o del token di aggiornamento per verificarne la validità.
  • Alla scadenza del token di accesso, il client utilizza il token di aggiornamento per aggiornare il token di accesso.
  • Durante la verifica del token di aggiornamento, il server controlla una piccola lista nera di ID utente, se rilevata rifiuta la richiesta di aggiornamento.
  • Quando un client non ha un aggiornamento o token di autenticazione valido (non scaduto), l'utente deve riconnettersi, poiché tutte le altre richieste verranno rifiutate.
  • Su richiesta di accesso, controllare l'archivio dati utente per il divieto.
  • Al logout: aggiungi quell'utente alla blacklist della sessione in modo che debbano accedere nuovamente. Dovresti archiviare informazioni aggiuntive per non disconnetterli da tutti i dispositivi in ​​un ambiente multi-dispositivo, ma puoi farlo aggiungendo un campo dispositivo al lista nera degli utenti.
  • Per forzare il rientro dopo x tempo, mantenere l'ultima data di accesso nel token di autenticazione e verificarla per richiesta.
  • Per forzare la disconnessione da tutti gli utenti, ripristinare la chiave hash token.

Ciò richiede di mantenere una lista nera (stato) sul server, presupponendo che la tabella utente contenga informazioni utente vietate. La lista nera delle sessioni non valide - è un elenco di ID utente. Questa lista nera viene verificata solo durante una richiesta di token di aggiornamento. Le voci sono tenute a viverci fino a quando il token di aggiornamento TTL. Una volta scaduto il token di aggiornamento, all'utente verrà richiesto di accedere nuovamente.

Contro:

  • È ancora necessario eseguire una ricerca nell'archivio dati sulla richiesta di token di aggiornamento.
  • I token non validi possono continuare a funzionare per il TTL del token di accesso.

Professionisti:

  • Fornisce la funzionalità desiderata.
  • L'azione di token di aggiornamento è nascosta all'utente durante il normale funzionamento.
  • È richiesto solo per effettuare una ricerca nell'archivio dati su richieste di aggiornamento anziché su ogni richiesta. ovvero 1 ogni 15 minuti anziché 1 al secondo.
  • Riduce a icona lo stato lato server in una lista nera molto piccola.

Con questa soluzione non è necessario un archivio di dati in memoria come reddis, almeno non per le informazioni dell'utente, poiché il server effettua solo una chiamata db ogni 15 minuti circa. Se si utilizza reddis, l'archiviazione di un elenco di sessioni valide / non valide sarebbe una soluzione molto semplice e veloce. Non è necessario un token di aggiornamento. Ogni token di autenticazione avrebbe un ID di sessione e un ID dispositivo, potrebbero essere memorizzati in una tabella reddis al momento della creazione e invalidati quando appropriato. Quindi verrebbero controllati su ogni richiesta e respinti se non validi.


Che dire dello scenario in cui una persona si alza da un computer per consentire a un'altra persona di utilizzare lo stesso computer? La prima persona si disconnetterà e si aspetterà che la disconnessione blocchi immediatamente la seconda persona. Se la seconda persona è un utente medio, il client potrebbe facilmente bloccare l'utente eliminando il token. Ma se il 2o utente ha abilità di hacking, l'utente ha il tempo di recuperare il token ancora valido per autenticarsi come 1o utente. Sembra che non vi sia alcun modo di evitare la necessità di invalidare immediatamente i token, senza indugio.
Joe Lapp

5
Oppure puoi rimuovere il tuo JWT dalla sessione / memoria locale o dai cookie.
Kamil Kiełczewski

1
Grazie @Ashtonian. Dopo aver fatto ricerche approfondite, ho abbandonato i JWT. A meno che non si faccia di tutto per proteggere la chiave segreta o se non si delega a un'implementazione OAuth sicura, i JWT sono molto più vulnerabili rispetto alle sessioni regolari. Vedi il mio rapporto completo: by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Joe Lapp,

2
L'uso di un token di aggiornamento è la chiave per consentire la blacklist. Grande spiegazione: auth0.com/blog/…
Rohmer,

1
Questa mi sembra la risposta migliore in quanto combina un token di accesso di breve durata con un token di aggiornamento di lunga durata che può essere inserito nella lista nera. Al logout, il client dovrebbe eliminare il token di accesso in modo che un secondo utente non possa ottenere l'accesso (anche se il token di accesso rimarrà valido per alcuni minuti dopo il logout). @Joe Lapp afferma che un hacker (secondo utente) ottiene il token di accesso anche dopo che è stato eliminato. Come?
M3RS,

14

Un approccio che ho preso in considerazione è di avere sempre un valore iat(rilasciato a) nel JWT. Quindi, quando un utente si disconnette, memorizza quel timestamp nel record dell'utente. Quando si convalida JWT, è sufficiente confrontare iatl'ultimo timestamp disconnesso. Se laiat è più vecchio, non è valido. Sì, devi andare al DB, ma tirerò sempre comunque il record utente se il JWT è altrimenti valido.

Il principale svantaggio che vedo a questo è che li disconnetterebbe da tutte le loro sessioni se si trovano su più browser o se hanno anche un client mobile.

Questo potrebbe anche essere un buon meccanismo per invalidare tutti i JWT in un sistema. Parte del controllo potrebbe essere a fronte di un timestamp globale dell'ultima iatvolta valida .


1
Buon pensiero! Per risolvere il problema di "un dispositivo" è necessario renderlo una funzionalità di emergenza piuttosto che un logout. Memorizza la data a sul record dell'utente che invalida tutti i token emessi prima di esso. Qualcosa del genere token_valid_aftero qualcosa del genere. Eccezionale!
OneHoopyFrood

1
Ehi @OneHoopyFrood hai un codice di esempio per aiutarmi a capire l'idea in un modo migliore? Apprezzo molto il vostro aiuto!
alexventuraio,

2
Come tutte le altre soluzioni proposte, questa richiede una ricerca nel database, motivo per cui esiste questa domanda perché evitare la ricerca è la cosa più importante qui! (Prestazioni, scalabilità). In circostanze normali non è necessaria una ricerca DB per disporre dei dati utente, ma sono già stati acquisiti dal client.
Rob Evans,

9

Sono un po 'in ritardo qui, ma penso di avere una soluzione decente.

Ho una colonna "last_password_change" nel mio database che memorizza la data e l'ora dell'ultima modifica della password. Inoltre, memorizzo la data / ora del problema nel JWT. Durante la convalida di un token, controllo se la password è stata modificata dopo l'emissione del token e se era token rifiutato anche se non è ancora scaduto.


1
Come si rifiuta il token? Puoi mostrare un breve codice di esempio?
alexventuraio,

1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Vanuan,

15
Richiede una ricerca db!
Rob Evans,

5

Puoi avere un campo "last_key_used" sul tuo DB nel documento / record dell'utente.

Quando l'utente accede con l'utente e passa, genera una nuova stringa casuale, memorizzala nel campo last_key_used e aggiungila al payload quando firma il token.

Quando l'utente accede utilizzando il token, selezionare last_key_used nel DB per corrispondere a quello nel token.

Quindi, quando l'utente esegue un logout, ad esempio, o se si desidera invalidare il token, è sufficiente modificare il campo "last_key_used" in un altro valore casuale e tutti i controlli successivi falliranno, costringendo quindi l'utente ad accedere con l'utente e a passare nuovamente.


Questa è la soluzione che ho preso in considerazione, ma presenta questi inconvenienti: (1) stai eseguendo una ricerca DB su ogni richiesta per verificare il caso (annullando il motivo dell'utilizzo dei token anziché delle sessioni) oppure controllo intermittente solo dopo che un token di aggiornamento è scaduto (impedendo agli utenti di disconnettersi immediatamente o di terminare immediatamente le sessioni); e (2) la disconnessione disconnette l'utente da tutti i browser e tutti i dispositivi (che non è un comportamento convenzionalmente previsto).
Joe Lapp,

Non è necessario cambiare la chiave quando l'utente si disconnette, solo quando cambiano la password o -se la fornite- quando scelgono di disconnettersi da tutti i dispositivi
NickVarcha

3

Mantieni un elenco in memoria come questo

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

Se i token scadono entro una settimana, pulisci o ignora i record precedenti. Conserva anche solo il record più recente di ciascun utente. La dimensione dell'elenco dipenderà dalla durata della conservazione dei token e dalla frequenza con cui gli utenti revocano i token. Usa db solo quando la tabella cambia. Carica la tabella in memoria all'avvio dell'applicazione.


2
La maggior parte dei siti di produzione viene eseguita su più di un server, quindi questa soluzione non funzionerà. L'aggiunta di Redis o cache interpocess simile complica in modo significativo il sistema e spesso porta più problemi delle soluzioni.
user2555515

@ user2555515 tutti i server possono essere sincronizzati con il database. È una tua scelta colpire il database ogni volta o no. Potresti dire quali problemi comporta.
Eduardo,

3

------------------------ Un po 'in ritardo per questa risposta, ma potrebbe essere di aiuto a qualcuno ------------- -----------

Dal lato client , il modo più semplice è rimuovere il token dalla memoria del browser.

Ma cosa succede se si desidera distruggere il token sul server Node -

Il problema con il pacchetto JWT è che non fornisce alcun metodo o modo per distruggere il token. È possibile utilizzare metodi diversi rispetto a JWT di cui sopra. Ma qui vado con i jwt-redis.

Quindi, al fine di distruggere il token sul lato server, è possibile utilizzare il pacchetto jwt-redis anziché JWT

Questa libreria (jwt-redis) ripete completamente l'intera funzionalità della libreria jsonwebtoken, con un'importante aggiunta. Jwt-redis consente di memorizzare l'etichetta del token in redis per verificarne la validità. L'assenza di un'etichetta token in redis rende il token non valido. Per distruggere il token in jwt-redis, esiste un metodo di distruzione

funziona in questo modo:

1) Installa jwt-redis da npm

2) Creare -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) Per verificare -

jwtr.verify(token, secret);

4) Distruggere -

jwtr.destroy(token)

Nota : è possibile fornire expiresIn durante l'accesso del token nello stesso modo in cui viene fornito in JWT.

Può essere questo aiuterà qualcuno


2

Perché non usare semplicemente il claim jti (nonce) e memorizzarlo in un elenco come campo record dell'utente (dipendente da db, ma almeno un elenco separato da virgole va bene)? Non c'è bisogno di una ricerca separata, come altri hanno sottolineato presumibilmente vuoi ottenere comunque il record dell'utente, e in questo modo puoi avere più token validi per diverse istanze client ("logout ovunque" può reimpostare l'elenco vuoto)


Si Questo. Forse creare una relazione uno-a-molti tra la tabella utente e una nuova tabella (sessione), in modo da poter memorizzare i metadati insieme alle attestazioni jti.
Peter Lada,

2
  1. Dare 1 giorno di scadenza per i token
  2. Mantenere una lista nera quotidiana.
  3. Inserisci i token non validi / di logout nella lista nera

Per la convalida del token, verificare prima la scadenza del token e quindi la blacklist se il token non è scaduto.

Per esigenze di sessioni lunghe, dovrebbe esserci un meccanismo per estendere il tempo di scadenza del token.


4
metti i token nella lista nera e arriva la tua apolidia
Kerem Baydoğan,

2

In ritardo alla festa, i MIEI due centesimi sono riportati di seguito dopo alcune ricerche. Durante il logout, assicurati che stiano accadendo le seguenti cose ...

Cancella la memoria / sessione del client

Aggiorna la data e l'ora dell'ultimo accesso della tabella utenti e la data e l'ora di disconnessione ogni volta che si verificano rispettivamente il login o il logout. Quindi l'ora della data di accesso dovrebbe sempre essere maggiore della disconnessione (oppure mantenere la data di disconnessione nullo se lo stato corrente è login e non ancora disconnesso)

Questo è molto più semplice che tenere una tabella aggiuntiva nella lista nera e spurgare regolarmente. Il supporto di più dispositivi richiede una tabella aggiuntiva per mantenere l'accesso, le date di disconnessione con alcuni dettagli aggiuntivi come i dettagli del sistema operativo o del client.


2

Unico per stringa utente e stringa globale con hash insieme

fungere da porzione segreta JWT per consentire l'invalidazione di token sia individuale che globale. Massima flessibilità al costo di una ricerca / lettura db durante l'autenticazione richiesta. Anche facile da memorizzare nella cache, poiché raramente cambiano.

Ecco un esempio:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

ad esempio, consultare https://jwt.io (non sono sicuri che gestiscano segreti dinamici a 256 bit)


1
Qualche dettaglio in più sarebbe sufficiente
giantas il

2
@giantas, penso che Mark significhi la parte della firma. Quindi, invece, usando solo una chiave singola per firmare il JWT, combinalo in modo univoco per ciascun client. Pertanto, se si desidera invalidare tutta la sessione di un utente, è sufficiente modificare la chiave per quell'utente e se si desidera invalidare tutta la sessione nel proprio sistema, è sufficiente modificare quella singola chiave globale.
Tommy Aria Pradana,

1

L'ho fatto nel modo seguente:

  1. Generare a unique hash, quindi memorizzarlo in redis e nel JWT . Questa può essere chiamata sessione
    • Memorizzeremo anche il numero di richieste fatte dal particolare JWT - Ogni volta che un jwt viene inviato al server, incrementiamo il numero intero di richieste . (questo è facoltativo)

Quindi quando un utente accede, viene creato un hash univoco, memorizzato in redis e iniettato nel tuo JWT .

Quando un utente tenta di visitare un endpoint protetto , acquisirai l' hash di sessione univoco dal tuo JWT , esegui una query su redis e vedi se è una corrispondenza!

Possiamo estenderlo e rendere il nostro JWT ancora più sicuro, ecco come:

Ogni X richiede una particolare JWT , generiamo una nuova sessione unica, la memorizziamo nella nostra JWT e quindi inseriamo nella lista nera la precedente.

Ciò significa che il JWT è in costante evoluzione e impedisce che il JWT stantio venga violato, rubato o qualcos'altro.


1
È possibile eseguire l'hashing del token stesso e archiviare quel valore in redis, anziché iniettare un nuovo hash nel token.
Frug

Guarda anche aude jtireclami in JWT, sei sulla strada giusta.
Peter Lada,

1

Se vuoi essere in grado di revocare i token utente, puoi tenere traccia di tutti i token emessi sul tuo DB e verificare se sono validi (esistono) su una tabella simile a una sessione. Il rovescio della medaglia è che colpirai il DB ad ogni richiesta.

Non l'ho provato, ma suggerisco il seguente metodo per consentire la revoca del token mantenendo al minimo gli hit del DB -

Per ridurre la percentuale di controlli del database, dividere tutti i token JWT emessi in gruppi X in base a un'associazione deterministica (ad esempio, 10 gruppi per prima cifra dell'ID utente).

Ogni token JWT conterrà l'id del gruppo e un timestamp creato al momento della creazione del token. per esempio,{ "group_id": 1, "timestamp": 1551861473716 }

Il server manterrà tutti gli ID di gruppo in memoria e ogni gruppo avrà un timestamp che indica quando è stato l'ultimo evento di disconnessione di un utente appartenente a quel gruppo. per esempio,{ "group1": 1551861473714, "group2": 1551861487293, ... }

Le richieste con un token JWT che hanno un timestamp di gruppo precedente, verranno verificate per la validità (hit DB) e, se valide, verrà emesso un nuovo token JWT con un nuovo timestamp per l'uso futuro del client. Se il timestamp del gruppo del token è più recente, ci fidiamo del JWT (nessun hit DB).

Così -

  1. Convalidiamo un token JWT utilizzando il DB se il token ha un vecchio timestamp di gruppo, mentre le richieste future non verranno convalidate fino a quando qualcuno nel gruppo dell'utente non si disconnetterà.
  2. Usiamo i gruppi per limitare il numero di modifiche al timestamp (diciamo che c'è un utente che accede e si disconnette come se non ci fosse un domani - influenzerà solo un numero limitato di utenti anziché tutti)
  3. Limitiamo il numero di gruppi per limitare la quantità di timestamp conservati in memoria
  4. Invalidare un token è un gioco da ragazzi: basta rimuoverlo dalla tabella della sessione e generare un nuovo timestamp per il gruppo dell'utente.

Lo stesso elenco può essere mantenuto in memoria (domanda per c #) ed eliminerebbe la necessità di colpire il db per ogni richiesta. L'elenco può essere caricato da db all'avvio dell'applicazione
dvdmn il

1

Se l'opzione "disconnetti da tutti i dispositivi" è accettabile (nella maggior parte dei casi lo è):

  • Aggiungi il campo versione token al record utente.
  • Aggiungi il valore in questo campo alle attestazioni archiviate nel JWT.
  • Incrementa la versione ogni volta che l'utente si disconnette.
  • Quando si convalida il token, confrontare la richiesta della sua versione con la versione memorizzata nel record dell'utente e rifiutare se non è la stessa.

Nella maggior parte dei casi è necessario comunque un viaggio db per ottenere il record dell'utente, quindi questo non aggiunge un sovraccarico al processo di convalida. A differenza del mantenimento di una lista nera, in cui il caricamento del DB è significativo a causa della necessità di utilizzare un join o una chiamata separata, pulire i vecchi record e così via.


0

Ho intenzione di rispondere se è necessario fornire la disconnessione da tutte le funzionalità dei dispositivi quando si utilizza JWT. Questo approccio utilizzerà le ricerche nel database per ogni richiesta. Perché è necessario uno stato di sicurezza della persistenza anche in caso di arresto anomalo del server. Nella tabella utente avremo due colonne

  1. LastValidTime (impostazione predefinita: ora di creazione)
  2. Accesso effettuato (impostazione predefinita: vero)

Ogni volta che vi è una richiesta di disconnessione da parte dell'utente, aggiorneremo LastValidTime all'ora corrente e Logged-In su false. Se è presente una richiesta di accesso, non cambieremo LastValidTime ma Logged-In sarà impostato su true.

Quando creiamo il JWT avremo il tempo di creazione del JWT nel payload. Quando autorizziamo un servizio, verificheremo 3 condizioni

  1. JWT è valido
  2. Il tempo di creazione del payload JWT è maggiore di LastValidTime utente
  3. L'utente è connesso

Vediamo uno scenario pratico.

L'utente X ha due dispositivi A, B. Ha effettuato l'accesso al nostro server alle 19:00 utilizzando il dispositivo A e il dispositivo B. (supponiamo che il tempo di scadenza di JWT sia di 12 ore). A e B hanno entrambi JWT con CreatedTime: 7pm

Alle 21.00 ha perso il suo dispositivo B. Si è immediatamente disconnesso dal dispositivo A. Ciò significa che la voce dell'utente del nostro database X ha LastValidTime come "ThatDate: 9: 00: xx: xxx" e Log-in come "false".

Alle 9:30 Mr.Thief tenta di accedere utilizzando il dispositivo B. Verificheremo il database anche se il Login è falso, quindi non lo consentiremo.

Alle 22:00 Mr.X accede dal suo dispositivo A. Ora il dispositivo A ha JWT con orario creato: 10pm. Ora il database registrato è impostato su "true"

Alle 22:30 Mr.Thief prova ad accedere. Anche se il Login è vero. LastValidTime è alle 21.00 nel database ma il JWT di B ha creato l'ora alle 19:00. Quindi non gli sarà permesso di accedere al servizio. Pertanto, utilizzando il dispositivo B senza avere la password non può utilizzare JWT già creato dopo il logout di un dispositivo.


0

Una soluzione IAM come Keycloak (su cui ho lavorato) fornisce un endpoint di revoca token simile

Endpoint di revoca token /realms/{realm-name}/protocol/openid-connect/revoke

Se si desidera semplicemente disconnettere un useragent (o un utente), è possibile chiamare anche un endpoint (questo invaliderebbe semplicemente i token). Ancora una volta, nel caso di Keycloak, il Relying Party deve solo chiamare l'endpoint

/realms/{realm-name}/protocol/openid-connect/logout

Link nel caso in cui desideri saperne di più


-1

Questo sembra davvero difficile da risolvere senza una ricerca DB su ogni verifica del token. L'alternativa a cui riesco a pensare è mantenere una lista nera di token non validi sul lato server; che dovrebbe essere aggiornato su un database ogni volta che si verifica una modifica che persiste le modifiche al riavvio, facendo in modo che il server controlli il database al riavvio per caricare la lista nera corrente.

Ma se lo mantieni nella memoria del server (una sorta di variabile globale), non sarà scalabile su più server se ne utilizzi più di uno, quindi in tal caso puoi tenerlo su una cache Redis condivisa, che dovrebbe essere configurato per persistere da qualche parte nei dati (database? filesystem?) nel caso in cui debba essere riavviato e ogni volta che viene avviato un nuovo server deve iscriversi alla cache Redis.

In alternativa a una lista nera, usando la stessa soluzione, puoi farlo con un hash salvato in redis per sessione come sottolinea questa altra risposta (non sono sicuro che sarebbe più efficiente con l'accesso di molti utenti).

Sembra terribilmente complicato? lo fa per me!

Disclaimer: non ho usato Redis.


-1

Se stai usando axios o una simile lib di richiesta HTTP basata su promesse puoi semplicemente distruggere il token sul front-end all'interno della .then()parte. Verrà avviato nella parte di risposta .then () dopo che l'utente ha eseguito questa funzione (il codice risultato dall'endpoint del server deve essere ok, 200). Dopo che l'utente ha fatto clic su questa route durante la ricerca di dati, se il campo del database user_enabledè falso, si innescherà la distruzione del token e l'utente verrà immediatamente disconnesso e impedirà l'accesso alle route / pagine protette. Non dobbiamo attendere la scadenza del token mentre l'utente è connesso in modo permanente.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

-3

Ho appena salvato il token nella tabella degli utenti, quando accedo all'utente aggiornerò il nuovo token e quando auth sarà uguale all'utente jwt corrente.

Penso che questa non sia la soluzione migliore ma che funzioni per me.


2
Certo che non è il migliore! Chiunque abbia accesso al db può facilmente impersonare qualsiasi utente.
user2555515

1
@ user2555515 Questa soluzione funziona correttamente se il token archiviato nel database è crittografato, proprio come qualsiasi password memorizzata nel database dovrebbe essere. C'è una differenza tra Stateless JWTe Stateful JWT(che è molto simile alle sessioni). Stateful JWTpuò trarre vantaggio dal mantenimento di una whitelist di token.
TheDarkIn1978,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.