2013-03-15 20 views
18

Ho una piccola API a cui vorrei aggiungere l'autenticazione. Mi piacerebbe essere in grado di generare chiavi API per i consumatori API; i consumatori possono quindi utilizzare includere le chiavi con le loro richieste di richieste.Esiste un modo accettato di utilizzare le chiavi API per l'autenticazione in Flask?

C'è una libreria Flask che fa qualcosa di simile? O c'è un modo tipico per farlo? Ho fatto una ricerca e mi sono davvero imbattuto in this, che in realtà non è molto approfondito. Sto cercando una biblioteca, se ce n'è una.

risposta

2

Il modo "tipico" per generare una chiave API è creare un UUID (in genere creando un hash MD5 di qualche sottoinsieme di informazioni utente + informazioni un po 'casuali (come l'ora corrente)).

Tutte le chiavi API, tuttavia, devono essere UUID. L'hash esadecimale creato da MD5 soddisfa questo requisito, ma esistono sicuramente altri metodi.

Dopo aver creato una chiave per l'utente, memorizzarla nel database come parte delle informazioni utente e verificare che la chiave (memorizzata in un cookie, in genere) corrisponda a ciò che si ha. I meccanismi attuali di questo sono (in qualche modo) descritti nella pagina a cui sei collegato.

+1

* in genere creando un hash MD5 di qualche sottoinsieme di informazioni utente + informazioni un po 'casuali (come l'ora corrente) * No, non farlo. Tutto quello che devo fare è immaginare quanti anni ha l'account di qualcuno e poi andare alle gare perché lo spazio delle chiavi che devo testare ora è molto, molto piccolo. –

+0

Come si ottiene l'informazione che l'ora in cui la chiave è stata creata fa parte dell'hash in primo luogo? E come fai a sapere a che parte contribuisce? – jknupp

+0

https://www.google.com/search?q=samy+how+i+met+your+girlfriend spiega alcuni esempi di busting entropy e alcuni altri hack. (Attenzione, è un po 'strano, ma è una presentazione tecnicamente valida.) –

8

Per le chiavi di autenticazione, creare un valore casuale e memorizzarlo in un database. random() fornisce entropia insufficiente per cose come questa, quindi utilizzare os.urandom().

Il link che hai postato ha un ottimo esempio di come gestire le cose con una funzione di decoratore. Nella funzione decoratore, verificare che il valore dell'appkey sia impostato nella richiesta, verificare che sia valido nel database e quindi restituire la funzione. Se il tasto app non è valido, raise AuthenticationError("Invalid appkey") e il gioco è fatto.

L'esempio a cui è collegato è un po 'confuso. Mi piace la dimostrazione da How to make a chain of function decorators? migliore.

def checkAppKey(fn): 
    def inner(*args, **kwargs): #appkey should be in kwargs 
     try: 
      AppKey.get(appkey) 
     except KeyError: 
      raise AuthenticationError("Invalid appkey") 
      #Whatever other errors can raise up such as db inaccessible 
     #We were able to access that API key, so pass onward. 
     #If you know nothing else will use the appkey after this, you can unset it. 
     return fn(*args, **kwargs) 
    return inner 
+0

Come si evitano le collisioni tra chiavi quando si "crea un valore casuale" semplicemente? – jknupp

+1

Ti affidi ad avere un generatore di numeri casuali sufficientemente buono che le probabilità siano quelle di un "Compleanno". Con uno spazio delle chiavi abbastanza grande (raccomando 128 bit come standard), le probabilità di collisione sono così basse che probabilmente incontrerai errori di bit in memoria. http://en.wikipedia.org/wiki/Birthday_problem#Probability_table –

+0

OK, ma probabilmente dovresti specificarlo nelle tue chiamate 'random' e' os.urandom' (entrambe le quali prendono la lunghezza desiderata, un dettaglio piuttosto importante). – jknupp

4

Ecco una funzione che utilizza hashlib che ha funzionato abbastanza bene per me:

def generate_hash_key(): 
    """ 
    @return: A hashkey for use to authenticate agains the API. 
    """ 
    return base64.b64encode(hashlib.sha256(str(random.getrandbits(256))).digest(), 
          random.choice(['rA', 'aZ', 'gQ', 'hH', 'hG', 'aR', 'DD'])).rstrip('==') 

Una possibile soluzione per implementare questo in app potrebbe essere applicando un decoratore su ogni percorso che si desidera proteggere.

Esempio:

def get_apiauth_object_by_key(key): 
    """ 
    Query the datastorage for an API key. 
    @param ip: ip address 
    @return: apiauth sqlachemy object. 
    """ 
    return model.APIAuth.query.filter_by(key=key).first() 

def match_api_keys(key, ip): 
    """ 
    Match API keys and discard ip 
    @param key: API key from request 
    @param ip: remote host IP to match the key. 
    @return: boolean 
    """ 
    if key is None or ip is None: 
     return False 
    api_key = get_apiauth_object_by_key(key) 
    if api_key is None: 
     return False 
    elif api_key.ip == "0.0.0.0": # 0.0.0.0 means all IPs. 
     return True 
    elif api_key.key == key and api_key.ip == ip: 
     return True 
    return False 

def require_app_key(f): 
    """ 
    @param f: flask function 
    @return: decorator, return the wrapped function or abort json object. 
    """ 

    @wraps(f) 
    def decorated(*args, **kwargs): 
     if match_api_keys(request.args.get('key'), request.remote_addr): 
     return f(*args, **kwargs) 
     else: 
     with log_to_file: 
      log.warning("Unauthorized address trying to use API: " + request.remote_addr) 
     abort(401) 
     return decorated 

E quindi è possibile utilizzare il decoratore in quanto tale:

@require_app_key 
def delete_cake(version, cake_id): 
    """ 
    Controller for API Function that gets a cake by ID 
    @param cake_id: cake id 
    @return: Response and HTTP code 
    """ 

Questo esempio utilizza SQLAlchemy per memorizzare le chiavi nel database (Si potrebbe utilizzare SQLite).

È possibile vedere l'implementazione qui: https://github.com/haukurk/flask-restapi-recipe.

+0

Le risposte Good Stackoverflow tendono a spiegare come fare qualcosa, piuttosto che limitarsi a un esempio. Spiega cosa sta facendo bene quell'esempio, piuttosto che dire semplicemente che è giusto. – Adam

+0

I collegamenti possono essere utili come informazioni supplementari, ma [le risposte solo per collegamento sono fortemente sconsigliate] (http://meta.stackoverflow.com/a/8259/228805). Includi un riepilogo delle informazioni collegate pertinenti alla domanda e spiega come risolve il problema. –

+0

Non utilizzare 'random.getrandbits()' per generare il tuo segreto, il generatore di numeri casuali non è crittograficamente forte. C'è un grosso avvertimento nei documenti per il modulo 'random' su questo: https://docs.python.org/2/library/random.html – dbader