REST API Autenticazione basata su token


122

Sto sviluppando un'API REST che richiede l'autenticazione. Poiché l'autenticazione stessa avviene tramite un servizio web esterno su HTTP, ho pensato che avremmo distribuito token per evitare di chiamare ripetutamente il servizio di autenticazione. Il che mi porta chiaramente alla mia prima domanda:

È davvero meglio che richiedere ai client di utilizzare l'autenticazione di base HTTP su ogni richiesta e memorizzare nella cache le chiamate al servizio di autenticazione lato server?

La soluzione Basic Auth ha il vantaggio di non richiedere un round trip completo al server prima che le richieste di contenuto possano iniziare. I token possono potenzialmente avere un ambito più flessibile (ovvero concedere solo diritti a risorse o azioni particolari), ma ciò sembra più appropriato al contesto OAuth rispetto al mio caso d'uso più semplice.

Attualmente i token vengono acquisiti in questo modo:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

I api_key, timestampe verifiersono richiesti da tutte le richieste. Il "verificatore" viene restituito da:

sha1(timestamp + api_key + shared_secret)

La mia intenzione è quella di consentire solo le chiamate da parti conosciute e di impedire che le chiamate vengano riutilizzate alla lettera.

È abbastanza buono? Underkill? Eccessivo?

Con un token in mano, i clienti possono acquisire risorse:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Per la più semplice chiamata possibile, questo sembra un po 'orribilmente prolisso. Considerando che la shared_secretvolontà finirà per essere incorporata (almeno) in un'applicazione iOS, da cui presumo possa essere estratta, questo offre qualcosa al di là di un falso senso di sicurezza?


2
Invece di usare sha1 (timestamp + api_key + shard_secret) dovresti usare hmac (shared_secret, timpestamp + api_key) per un migliore hashing di sicurezza en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco

@ MiguelA.Carrasco E sembra esserci il consenso nel 2017 sul fatto che bCrypt sia il nuovo strumento di hashing.
Shawn

Risposte:


94

Fammi separare tutto e risolvere ogni problema in modo isolato:

Autenticazione

Per l'autenticazione, baseauth ha il vantaggio di essere una soluzione matura a livello di protocollo. Ciò significa che molti problemi "potrebbero sorgere in seguito" sono già risolti per te. Ad esempio, con BaseAuth, i programmi utente sanno che la password è una password, quindi non la memorizzano nella cache.

Caricamento del server di autenticazione

Se distribuisci un token all'utente invece di memorizzare nella cache l'autenticazione sul tuo server, stai ancora facendo la stessa cosa: memorizzare nella cache le informazioni di autenticazione. L'unica differenza è che stai trasferendo la responsabilità della memorizzazione nella cache all'utente. Questo sembra un lavoro inutile per l'utente senza guadagni, quindi consiglio di gestirlo in modo trasparente sul tuo server come hai suggerito.

Sicurezza della trasmissione

Se è possibile utilizzare una connessione SSL, è tutto ciò che serve, la connessione è sicura *. Per impedire l'esecuzione multipla accidentale, puoi filtrare più URL o chiedere agli utenti di includere un componente casuale ("nonce") nell'URL.

url = username:key@myhost.com/api/call/nonce

Se ciò non è possibile e le informazioni trasmesse non sono segrete, consiglio di proteggere la richiesta con un hash, come suggerito nell'approccio token. Poiché l'hash fornisce la sicurezza, è possibile istruire i propri utenti a fornire l'hash come password di base. Per una maggiore robustezza, consiglio di utilizzare una stringa casuale invece del timestamp come "nonce" per prevenire attacchi di replay (due richieste legittime potrebbero essere fatte durante lo stesso secondo). Invece di fornire campi separati "segreto condiviso" e "chiave api", puoi semplicemente utilizzare la chiave api come segreto condiviso e quindi utilizzare un salt che non cambia per prevenire attacchi alla tabella arcobaleno. Il campo del nome utente sembra un buon posto dove mettere anche il nonce, poiché fa parte dell'autenticazione. Quindi ora hai una chiamata pulita come questa:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:one_time_key@myhost.com/api/call

È vero che questo è un po 'laborioso. Questo perché non stai utilizzando una soluzione a livello di protocollo (come SSL). Quindi potrebbe essere una buona idea fornire una sorta di SDK agli utenti in modo che almeno non debbano esaminarlo da soli. Se hai bisogno di farlo in questo modo, trovo il livello di sicurezza appropriato (just-right-kill).

Archiviazione segreta sicura

Dipende da chi stai cercando di contrastare. Se stai impedendo alle persone con accesso al telefono dell'utente di utilizzare il tuo servizio REST a nome dell'utente, sarebbe una buona idea trovare un qualche tipo di API portachiavi sul sistema operativo di destinazione e fare in modo che l'SDK (o l'implementatore) memorizzi il chiave lì. Se ciò non è possibile, puoi almeno rendere un po 'più difficile ottenere il segreto crittografandolo e archiviando i dati crittografati e la chiave di crittografia in luoghi separati.

Se stai cercando di impedire ad altri fornitori di software di ottenere la tua chiave API per impedire lo sviluppo di client alternativi, solo l'approccio crittografa e archivia separatamente funziona quasi . Questa è la crittografia whitebox e, ad oggi, nessuno ha trovato una soluzione veramente sicura ai problemi di questa classe. Il minimo che puoi fare è comunque emettere una singola chiave per ogni utente in modo da poter vietare le chiavi abusate.

(*) EDIT: le connessioni SSL non dovrebbero più essere considerate sicure senza prendere ulteriori passaggi per verificarle .


Grazie cmc, tutti gli aspetti positivi e ottimi spunti di riflessione. Ho finito per adottare un approccio token / HMAC simile a quello che hai discusso sopra, piuttosto come il meccanismo di autenticazione dell'API REST S3 .
cantlin

Se memorizzi nella cache il token sul server, non è essenzialmente lo stesso del buon vecchio ID di sessione? L'ID di sessione è di breve durata ed è anche collegato alla memoria cache veloce (se implementata) per evitare di colpire il database a ogni richiesta. Il vero design RESTful e senza stato non dovrebbe avere sessioni, ma se stai usando un token come ID e poi continui a colpire il DB, allora non sarebbe meglio usare semplicemente l'ID di sessione? In alternativa, puoi utilizzare i token Web JSON che contengono informazioni crittografate o firmate per i dati dell'intera sessione per un vero design senza stato.
JustAMartin

16

Un'API RESTful pura dovrebbe utilizzare le funzionalità standard del protocollo sottostante:

  1. Per HTTP, l'API RESTful deve essere conforme alle intestazioni standard HTTP esistenti. L'aggiunta di una nuova intestazione HTTP viola i principi REST. Non reinventare la ruota, utilizza tutte le funzionalità standard negli standard HTTP / 1.1, inclusi codici di risposta di stato, intestazioni e così via. I servizi Web RESTFul dovrebbero sfruttare e fare affidamento sugli standard HTTP.

  2. I servizi RESTful DEVONO essere STATELESS. Qualsiasi trucco, come l'autenticazione basata su token che tenta di ricordare lo stato delle precedenti richieste REST sul server, viola i principi REST. Ancora una volta, questo è un MUST; cioè, se il tuo server web salva qualsiasi informazione relativa al contesto di richiesta / risposta sul server nel tentativo di stabilire qualsiasi tipo di sessione sul server, il tuo servizio web NON è Stateless. E se NON è apolide NON è RESTFul.

Conclusione: per scopi di autenticazione / autorizzazione è necessario utilizzare l'intestazione di autorizzazione standard HTTP. Cioè, dovresti aggiungere l'intestazione di autorizzazione / autenticazione HTTP in ogni richiesta successiva che deve essere autenticata. L'API REST deve seguire gli standard dello schema di autenticazione HTTP. Le specifiche di come questa intestazione deve essere formattata sono definite negli standard RFC 2616 HTTP 1.1 - sezione 14.8 Autorizzazione di RFC 2616 e nell'autenticazione HTTP RFC 2617: autenticazione di accesso di base e digest .

Ho sviluppato un servizio RESTful per l'applicazione Cisco Prime Performance Manager. Cerca su Google il documento API REST che ho scritto per quell'applicazione per maggiori dettagli sulla conformità API RESTFul qui . In tale implementazione, ho scelto di utilizzare lo schema di autorizzazione HTTP "di base". - controlla la versione 1.5 o successiva di quel documento API REST e cerca l'autorizzazione nel documento.


8
"L'aggiunta di una nuova intestazione HTTP viola i principi REST" In che modo? E se ci sei potresti essere così gentile da spiegare qual è esattamente la differenza (riguardo ai principi) tra una password che scade dopo un certo periodo e un token che scade dopo un certo periodo.
un oliver migliore

6
Nome utente + password è un token (!) Che viene scambiato tra un client e un server ad ogni richiesta. Quel token viene mantenuto sul server e ha un tempo di vita. Se la password scade devo acquisirne una nuova. Sembra che tu associ "token" a "sessione server", ma questa è una conclusione non valida. È persino irrilevante perché sarebbe un dettaglio di implementazione. La tua classificazione di token diversi da nome utente / password come stateful è puramente artificiale, imho.
un migliore oliver

1
Penso che dovresti motivare il motivo per cui eseguire un'implementazione con RESTful sull'autenticazione di base che fa parte della domanda originale. Forse potresti anche collegarti ad alcuni buoni esempi con codice incluso. Come principiante in questo argomento, la teoria sembra abbastanza chiara con molte buone risorse, ma il metodo di implementazione non lo è e gli esempi sono contorti. Trovo frustrante che sembri prendere codice personalizzato per implementare in modo tempestivo qualcosa che è stato fatto migliaia di volte.
JPK

13
-1 "Qualsiasi trucco, come l'autenticazione basata su token che tenta di ricordare lo stato delle precedenti richieste REST sul server, viola i principi REST." l'autenticazione basata su token non ha nulla a che fare con lo stato delle precedenti richieste REST e non viola l'apolidia di REST .
Kerem Baydoğan

1
Quindi, in base a questo, i token Web JSON rappresentano una violazione REST perché possono memorizzare lo stato dell'utente (attestazioni)? Ad ogni modo, preferisco violare REST e utilizzare il buon vecchio ID di sessione come "token", ma l'autenticazione iniziale viene eseguita con nome utente + pass, firmata o crittografata utilizzando un segreto condiviso e un timestamp di breve durata (quindi fallisce se qualcuno prova a riprodurre quello). In un'applicazione "aziendale" è difficile buttare via i vantaggi della sessione (evitando di colpire il database per alcuni dati necessari in quasi tutte le richieste), quindi a volte dobbiamo sacrificare la vera apolidia.
JustAMartin

2

Nel web un protocollo stateful si basa sull'avere un token temporaneo che viene scambiato tra un browser e un server (tramite l'intestazione del cookie o la riscrittura dell'URI) ad ogni richiesta. Quel token viene solitamente creato sul lato server ed è un pezzo di dati opaco che ha un certo tempo di vita e ha l'unico scopo di identificare uno specifico agente utente web. Cioè, il token è temporaneo e diventa uno STATO che il server web deve mantenere per conto di un agente utente client durante la durata di quella conversazione. Pertanto, la comunicazione che utilizza un token in questo modo è STATEFUL. E se la conversazione tra client e server è STATEFUL non è RESTful.

Il nome utente / password (inviati nell'intestazione di autorizzazione) vengono solitamente conservati nel database con l'intento di identificare un utente. A volte l'utente potrebbe indicare un'altra applicazione; tuttavia, il nome utente / password NON è MAI inteso per identificare uno specifico agente utente del client Web. La conversazione tra un agente Web e un server basata sull'utilizzo del nome utente / password nell'intestazione di autorizzazione (dopo l'autorizzazione di base HTTP) è STATELESS perché il front-end del server Web non crea o mantiene alcuna informazione STATO per conto di uno specifico agente utente del client Web. E in base alla mia comprensione di REST, il protocollo afferma chiaramente che la conversazione tra client e server dovrebbe essere STATELESS. Pertanto, se vogliamo avere un vero servizio RESTful dovremmo usare username / password (fare riferimento a RFC menzionato nel mio post precedente) nell'intestazione di autorizzazione per ogni singola chiamata, NON un tipo di token sension (ad esempio token di sessione creati nei server web , Token OAuth creati nei server di autorizzazione e così via).

Sono consapevole del fatto che diversi provider REST denominati utilizzano token come token di accettazione OAuth1 o OAuth2 da passare come "Autorizzazione: portatore" nelle intestazioni HTTP. Tuttavia, mi sembra che l'utilizzo di tali token per i servizi RESTful violerebbe il vero significato STATELESS che REST abbraccia; perché quei token sono pezzi temporanei di dati creati / mantenuti sul lato server per identificare uno specifico agente utente del client web per la durata valida di una conversazione client / server web. Pertanto, qualsiasi servizio che utilizza quei token OAuth1 / 2 non dovrebbe essere chiamato REST se vogliamo attenerci al vero significato di un protocollo STATELESS.

Rubens

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.