Autenticazione REST ed esposizione della chiave API


93

Ho letto su REST e ci sono molte domande su SO a riguardo, così come su molti altri siti e blog. Anche se non ho mai visto questa domanda specifica fatta ... per qualche motivo, non riesco a pensare a questo concetto ...

Se sto creando un'API RESTful e voglio proteggerla, uno dei metodi che ho visto è usare un token di sicurezza. Quando ho usato altre API, c'è stato un token e un segreto condiviso ... ha senso. Quello che non capisco è che le richieste per un'operazione del servizio di riposo vengono effettuate tramite javascript (XHR / Ajax), cosa serve per impedire a qualcuno di fiutarlo con qualcosa di semplice come FireBug (o "visualizza sorgente" nel browser) e copiare la chiave API e quindi impersonare quella persona utilizzando la chiave e il segreto?


uno dei metodi che ho visto è usare un token di sicurezza , ci sono davvero molti metodi là fuori. Hai un esempio concreto. Potrei pensare che ti confondi con "REST" e "rendere disponibile un'API javascript solo per gli utenti registrati" (ex google maps).
PeterMmm

1
Da quando hai chiesto quasi 2 anni fa: cosa hai usato alla fine da solo?
Arjan

In realtà non ho usato nulla, stavo solo cercando di capire come creare i concetti. Il commento di PeterMmm sopra è probabilmente vero ... non ho ancora avuto bisogno di implementare nulla di tutto ciò, ma volevo migliorare me stesso ... grazie per il seguito.
tjans

Risposte:


22

il segreto api non viene passato esplicitamente, il segreto viene utilizzato per generare un segno della richiesta corrente, sul lato server, il server genera il segno seguendo lo stesso processo, se i due segni corrispondono, la richiesta viene autenticata correttamente, quindi solo il il segno viene passato attraverso la richiesta, non il segreto.


9
Quindi, se è solo il segno che è passato ... non è ancora esposto in javascript ... quindi se metto una foto tremolante sulla mia pagina web tramite la loro API (chiamata da javascript) e visiti la mia pagina, non sono ' t espongo la mia chiave API a chiunque visiti la mia pagina?
tjans

6
Non penso di porre la mia domanda correttamente ... probabilmente parte del motivo per cui non stavo cercando quello che stavo cercando in primo luogo. quando effettuo la mia chiamata ajax, ad esempio usando jquery, dovrei incorporare la chiave API nella chiamata ajax in modo che venga passata al server ... a quel punto qualcuno può vedere la chiave API. Se ho capito male, come viene inviata la chiave API con la richiesta se non è incorporata nello script del client?
tjans

4
per concludere: alle persone verrà assegnata una coppia apikey + apisecret prima di utilizzare un openapi / restapi, il segno apikey + verrà trasferito sul lato server per assicurarsi che il server sappia chi sta facendo la richiesta, l'apisecret non sarà mai trasferito sul lato server per sicurezza .
James.Xu

7
Quindi l'affermazione di @ James.Xu che "il segreto viene utilizzato per generare un segno della richiesta corrente" è FALSA! Perché il cliente non conosce il segreto, perché non sarebbe sicuro inviarglielo (e come lo saprebbe altrimenti?) Il "segreto" che tecnicamente è una "chiave privata" viene utilizzato SOLO DAL SERVER (perché nessun altro lo sa) per generare un segno da confrontare con il segno del cliente. Quindi la domanda: che tipo di dati vengono combinati con la "chiave API" che nessun altro conosce oltre al client e al server? Segno = api_key + cosa?
AC

1
Hai ragione, @ACs. Anche se entrambi i server (il sito Web e l'API di terze parti) conoscono lo stesso segreto, non è possibile calcolare una firma sul server del sito Web e quindi inserire il risultato in HTML / JavaScript, quindi fare in modo che il browser lo passi all'API. In questo modo, qualsiasi altro server potrebbe richiedere quell'HTML dal primo server web, ottenere la firma dalla risposta e utilizzarla nell'HTML sul proprio sito web. (Penso davvero che il post sopra non risponda alla domanda su come una chiave API pubblica nell'HTML possa essere sicura.)
Arjan

61

Stiamo esponendo un'API che i partner possono utilizzare solo sui domini che hanno registrato con noi. Il suo contenuto è in parte pubblico (ma preferibilmente da mostrare solo sui domini che conosciamo), ma è per lo più privato per i nostri utenti. Così:

  • Per determinare cosa viene mostrato, il nostro utente deve essere loggato con noi, ma questo viene gestito separatamente.

  • Per determinare dove vengono mostrati i dati, viene utilizzata una chiave API pubblica per limitare l'accesso ai domini che conosciamo e soprattutto per garantire che i dati degli utenti privati ​​non siano vulnerabili a CSRF .

Questa chiave API è effettivamente visibile a chiunque, non autentichiamo il nostro partner in nessun altro modo e non abbiamo bisogno di REFERER . Tuttavia, è sicuro:

  1. Quando get-csrf-token.js?apiKey=abc123viene richiesto il nostro :

    1. Cerca la chiave abc123nel database e ottieni un elenco di domini validi per quella chiave.

    2. Cerca il cookie di convalida CSRF. Se non esiste, genera un valore casuale sicuro e inseriscilo in un cookie di sessione solo HTTP . Se il cookie esisteva, ottieni il valore casuale esistente.

    3. Crea un token CSRF dalla chiave API e il valore casuale dal cookie e firmalo . (Invece di mantenere un elenco di token sul server, stiamo firmando i valori. Entrambi i valori saranno leggibili nel token firmato, va bene.)

    4. Imposta la risposta in modo che non venga memorizzata nella cache, aggiungi il cookie e restituisci uno script come:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }

    Appunti:

    • Quanto sopra non impedisce a uno script lato server di simulare una richiesta, ma garantisce solo che il dominio corrisponda se richiesto da un browser.

    • La stessa politica di origine per JavaScript garantisce che un browser non possa utilizzare XHR (Ajax) per caricare e quindi ispezionare l'origine JavaScript. Invece, un normale browser può caricarlo solo utilizzando <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(o un equivalente dinamico) e quindi eseguirà il codice. Ovviamente, il tuo server non dovrebbe supportare la condivisione delle risorse tra le origini né JSONP per JavaScript generato.

    • Uno script del browser può modificare il valore di document.domainprima di caricare lo script precedente. Ma la stessa politica di origine consente solo di accorciare il dominio rimuovendo i prefissi, come la riscrittura subdomain.example.comin solo example.com, o myblog.wordpress.comin wordpress.com, o in alcuni browser anche bbc.co.ukin co.uk.

    • Se il file JavaScript viene recuperato utilizzando uno script lato server, anche il server riceverà il cookie. Tuttavia, un server di terze parti non può fare in modo che il browser di un utente associ quel cookie al nostro dominio. Quindi, un token CSRF e un cookie di convalida che sono stati recuperati utilizzando uno script lato server, possono essere utilizzati solo dalle successive chiamate lato server, non in un browser. Tuttavia, tali chiamate lato server non includeranno mai il cookie dell'utente e quindi possono solo recuperare dati pubblici. Si tratta degli stessi dati che uno script lato server potrebbe raccogliere direttamente dal sito Web del partner.

  2. Quando un utente accede, imposta alcuni cookie utente nel modo che preferisci. (L'utente potrebbe aver già effettuato l'accesso prima che JavaScript fosse richiesto.)

  3. Tutte le successive richieste API al server (comprese le richieste GET e JSONP) devono includere il token CSRF, il cookie di convalida CSRF e (se connesso) il cookie dell'utente. Il server può ora determinare se la richiesta deve essere considerata attendibile:

    1. La presenza di un token CSRF valido garantisce che JavaScript sia stato caricato dal dominio previsto, se caricato da un browser.

    2. La presenza del token CSRF senza il cookie di convalida indica un falso.

    3. La presenza sia del token CSRF che del cookie di convalida CSRF non garantisce nulla: potrebbe trattarsi di una richiesta lato server falsificata o di una richiesta valida da un browser. (Non potrebbe essere una richiesta da un browser effettuata da un dominio non supportato.)

    4. La presenza del cookie utente garantisce che l'utente sia connesso, ma non garantisce che l'utente sia un membro del partner specificato, né che l'utente stia visualizzando il sito Web corretto.

    5. La presenza del cookie dell'utente senza il cookie di convalida CSRF indica un falso.

    6. La presenza del cookie dell'utente garantisce che la richiesta corrente venga effettuata tramite un browser. (Supponendo che un utente non inserisca le proprie credenziali su un sito Web sconosciuto e supponendo che non ci interessi che gli utenti utilizzino le proprie credenziali per effettuare alcune richieste lato server.) Se abbiamo anche il cookie di convalida CSRF, allora quel cookie di convalida CSRF era ricevuto anche utilizzando un browser. Successivamente, se abbiamo anche un token CSRF con una firma valida, eil numero casuale nel cookie di convalida CSRF corrisponde a quello in quel token CSRF, quindi anche il JavaScript per quel token è stato ricevuto durante quella stessa richiesta precedente durante la quale è stato impostato il cookie CSRF, quindi anche utilizzando un browser. Ciò quindi implica anche che il codice JavaScript sopra è stato eseguito prima che il token fosse impostato e che in quel momento il dominio era valido per la chiave API fornita.

      Quindi: il server ora può utilizzare in sicurezza la chiave API dal token firmato.

    7. Se in qualsiasi momento il server non si fida della richiesta, viene restituito un 403 Forbidden. Il widget può rispondere a ciò mostrando un avviso all'utente.

Non è necessario firmare il cookie di convalida CSRF, poiché lo stiamo confrontando con il token CSRF firmato. La mancata firma del cookie rende ogni richiesta HTTP più breve e la convalida del server un po 'più veloce.

Il token CSRF generato è valido a tempo indeterminato, ma solo in combinazione con il cookie di convalida, quindi in modo efficace fino alla chiusura del browser.

Potremmo limitare la durata della firma del token. Potremmo eliminare il cookie di convalida CSRF quando l'utente si disconnette, per soddisfare la raccomandazione OWASP . E per non condividere il numero casuale per utente tra più partner, è possibile aggiungere la chiave API al nome del cookie. Ma anche in questo caso non è possibile aggiornare facilmente il cookie di convalida CSRF quando viene richiesto un nuovo token, poiché gli utenti potrebbero navigare nello stesso sito in più finestre, condividendo un singolo cookie (che, durante l'aggiornamento, verrebbe aggiornato in tutte le finestre, dopodiché il Il token JavaScript nelle altre finestre non corrisponderebbe più a quel singolo cookie).

Per coloro che utilizzano OAuth, vedere anche OAuth e widget lato client , da cui ho preso l'idea JavaScript. Per l' utilizzo lato server dell'API, in cui non possiamo fare affidamento sul codice JavaScript per limitare il dominio, stiamo usando chiavi segrete invece delle chiavi API pubbliche.


1
Quando si utilizza CORS, forse è possibile estenderlo in sicurezza. Invece di quanto sopra, quando si gestisce una OPTIONSrichiesta pre-flight con una chiave API pubblica nell'URL, il server potrebbe indicare a un browser quali domini sono consentiti (o annullare la richiesta). Attenzione però che alcune richieste non richiedono una richiesta pre-flight, o non useranno affatto CORS , e che CORS necessita di IE8 +. Se per IE7 viene utilizzato un fallback Flash, forse un po 'di dinamica crossdomain.xmlpuò aiutare a ottenere lo stesso risultato. Non abbiamo ancora provato CORS / Flash.
Arjan

10

Questa domanda ha una risposta accettata, ma solo per chiarire, l'autenticazione del segreto condiviso funziona in questo modo:

  1. Il client ha una chiave pubblica, può essere condivisa con chiunque, non importa, quindi puoi incorporarla in javascript. Viene utilizzato per identificare l'utente sul server.
  2. Il server ha una chiave segreta e questo segreto DEVE essere protetto. Pertanto, l'autenticazione della chiave condivisa richiede che tu possa proteggere la tua chiave segreta. Quindi un client javascript pubblico che si connette direttamente a un altro servizio non è possibile perché è necessario un intermediario del server per proteggere il segreto.
  3. Il server firma la richiesta utilizzando un algoritmo che include la chiave segreta (la chiave segreta è una specie di sale) e preferibilmente un timestamp invia la richiesta al servizio. Il timestamp serve a prevenire gli attacchi "replay". La firma di una richiesta è valida solo per circa n secondi. Puoi verificarlo sul server ottenendo l'intestazione del timestamp che dovrebbe contenere il valore del timestamp incluso nella firma. Se il timestamp è scaduto, la richiesta non riesce.
  4. Il servizio riceve la richiesta che contiene non solo la firma ma anche tutti i campi che sono stati firmati in testo normale.
  5. Il servizio quindi firma la richiesta nello stesso modo utilizzando la chiave segreta condivisa e confronta le firme.

Vero, ma in base alla progettazione la tua risposta non espone la chiave API. Tuttavia, in alcune API la chiave API è pubblicamente visibile, ed è proprio su questo che si poneva la domanda: "richieste a un'operazione di rest service [...] effettuate tramite javascript (XHR / Ajax)" . (La risposta accettata è sbagliata anche su questo, credo; il tuo punto 2 è chiaro su questo, bene.)
Arjan

1

Suppongo che tu intenda chiave di sessione non chiave API. Questo problema è ereditato dal protocollo http e noto come dirottamento della sessione . La normale "soluzione alternativa" è, come in qualsiasi sito web, passare a https.

Per eseguire il servizio REST in modo sicuro è necessario abilitare https e probabilmente l'autenticazione del client. Ma dopotutto, questo va oltre l'idea REST. REST non parla mai di sicurezza.


8
In realtà intendevo la chiave. Se ricordo bene, per utilizzare un'API, stai passando la chiave API e il segreto al servizio di rest per l'autenticazione, corretto? So che una volta passato in rete verrebbe crittografato da SSL, ma prima che venga inviato, è perfettamente visibile dal codice client che lo utilizza ...
tjans

1

Quello che vuoi fare sul lato server è generare un ID di sessione in scadenza che viene inviato al client al momento dell'accesso o della registrazione. Il client può quindi utilizzare quell'ID di sessione come segreto condiviso per firmare le richieste successive.

L'id di sessione viene passato solo una volta e DEVE essere su SSL.

Vedi esempio qui

Utilizzare un nonce e un timestamp quando si firma la richiesta per impedire il dirottamento della sessione.


1
Ma come può esserci un accesso quando una terza parte utilizza la tua API? Se l'utente accede, allora le cose sono facili: usi solo una sessione? Ma quando altri siti web devono autenticarsi con la tua API, ciò non aiuta. (Inoltre, questo odora molto come promuovere il tuo blog.)
Arjan

1

Cercherò di rispondere alla domanda nel suo contesto originale. Quindi la domanda è "La chiave segreta (API) può essere inserita in modo sicuro in JavaScript.

A mio parere è molto pericoloso in quanto vanifica lo scopo dell'autenticazione tra i sistemi. Poiché la chiave sarà esposta all'utente, l'utente potrebbe recuperare le informazioni a cui non è autorizzato. Perché in un tipico riposo l'autenticazione della comunicazione si basa solo sulla chiave API.

Una soluzione secondo me è che la chiamata JavaScript passa essenzialmente la richiesta a un componente server interno che è responsabile di effettuare una chiamata di riposo. Il componente server interno diciamo che un servlet leggerà la chiave API da una fonte protetta come un file system basato sui permessi, la inserirà nell'intestazione HTTP ed effettuerà la chiamata rest esterna.

Spero che aiuti.

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.