2015-07-08 12 views
7

Sto usando un server gunicorn in cui sto cercando di capire un modo per limitare solo una sessione per nome utente, cioè se l'utente A è connesso all'app da Chrome, non dovrebbe essere in grado di accedere tramite Firefox a meno che non si disconnetta di chrome, o non dovrebbe essere in grado di aprire un altro TAB in chrome stesso.Come limitare una sessione da qualsiasi browser per un nome utente nel pallone?

Come posso generare un ID univoco per il browser e memorizzarlo in un DB in modo che fino a quando l'utente non si disconnette o la sessione scada, l'utente non può accedere tramite altri browser.

risposta

7

Un possibile metodo di limitazione delle sessioni a una singola scheda comporta la creazione di un token casuale al caricamento della pagina e l'incorporamento di questo token nella pagina. Questo token generato più di recente viene memorizzato anche nella sessione dell'utente. Questo sarà simile al modo in cui i vari framework aggiungono i token di convalida per prevenire gli attacchi CSFR.

Breve esempio: pagina carichi

  • utente nella scheda 1 in Firefox. Token1 viene generato, incorporato e memorizzato nella sessione
  • L'utente carica la pagina nella scheda 2 in Firefox. Token2 viene generato, incorporato e archiviato in sessione. Questo sovrascrive il valore precedente.
  • L'utente carica la pagina nella scheda 1 in Chrome. Token3 viene generato, incorporato e archiviato in sessione. questo sovrascrive il valore precedente.

A questo punto, l'utente ha la pagina aperta in 3 schede. La sessione dell'utente, tuttavia, ha solo Token3 memorizzati. Questo metodo impedisce all'utente di essere bloccato (diversi indirizzi IP, diverse stringhe di user agent, modalità di navigazione in incogneto, ecc.) Perché ogni nuova sessione genera semplicemente un nuovo token. Il carico più recente diventa la finestra attiva, invalidando immediatamente tutte le sessioni precedenti.

Successivamente, ogni volta che la pagina interagisce con il server (fa clic su un collegamento, invia dati, ecc.), Viene inviato anche il token incorporato nella pagina. Il server convalida che il token passato corrisponde al token nella sessione. Se corrispondono, l'azione ha successo. Se non corrispondono, il server restituisce un messaggio di errore.


È possibile generare numeri casuali in più modi, ma probabilmente si desidera qualcosa di sicuro.Useremo il example da un'altra domanda:

import string 
import random 
... 
N = 20 # Length of the string we want, adjust as appropriate 
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N)) 

Questo utilizza random.SystemRandom, che è più sicuro di semplicemente utilizzando random.choice


al caricamento della pagina, è necessario controllare se il token esistente è valido , genera il token casuale e lo memorizza nella sessione dell'utente. Dal momento che lo vogliamo ovunque, facciamo prima un decoratore, per ridurre il codice duplicato in un secondo momento. Il decoratore controlla se la sessione è valida e se non è possibile selezionare cosa fare (inserire la propria logica). Imposta anche un token di sessione. Questo è necessario (o hai bisogno di logica per escludere la tua pagina principale) altrimenti colpirai un ciclo infinito in cui l'utente tenta di caricare la pagina principale, non ha un token, fallisce e il processo si ripete. Ho il token che si rigenera su ogni caricamento della pagina tramite la clausola else. Se non si implementa la sezione if, questo decoratore è inutile in quanto entrambi i percorsi eseguono la stessa azione e semplicemente ripristinano il token al caricamento della pagina. La logica nel if è ciò che impedirà all'utente di avere più sessioni.

from flask import session 
from functools import wraps 

def random_string(length): 
    return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length)) 

def validate_token(f): 
    @wraps(f) 
    def wrapper(existing_token, *args, **kwargs): 
     if session['token'] != existing_token: 
      # Logic on failure. Do you present a 404, do you bounce them back to your main page, do you do something else? 
      # It is IMPORTANT that you determine and implement this logic 
      # otherwise the decorator simply changes the token (and behaves the same way as the else block). 
      session['token'] = random_string(20) 
     else: 
      session['token'] = random_string(20) 
     return f(*args, **kwargs) 
    return wrapper 

Now nei nostri percorsi, si può applicare questa decoratore a ciascuno, in modo che la sessione utente viene aggiornato ad ogni caricamento della pagina:

from flask import render_template 

@app.route('/path') 
@validate_token 
def path(token=None): 
    return render_template('path.html', token=session['token']) 

Nel modello, si desidera utilizzare questo valore token ovunque sia necessario per impedire che la sessione continui. Ad esempio, mettilo su collegamenti, in moduli (anche se Flask has a method of CSRF protection già), ecc. Il server stesso può controllare se il token passato è valido. Il modello potrebbe essere così semplice:

<a href="{{ url_for('path', token=token) }}">Click here to continue</a> 
2

Non so come implementarlo esattamente, ma sembra che è necessario websockets (Vedi here)

In questo modo, ad ogni caricamento della pagina, si potrebbe avere un Ajax richiesta al server, che avrebbe utilizzare la funzionalità websocket per interrogare la scheda del browser precedentemente aperta +, se presente. Se ha una risposta, significa che c'è un altro browser + scheda aperta. Il server dovrebbe quindi restituire queste informazioni.

Lo script che ha chiamato il server tramite Ajax, a seconda del reso, dovrebbe quindi decidere di reindirizzare a una pagina di errore o di continuare a caricarlo.

Non ho mai usato Websocket, ma sono abbastanza sicuro che ciò funzionerebbe (come lo è is well implemented in most browsers now).

+0

Sei a conoscenza di un modo per farlo usando la cache? – user2601010

+0

No ... Non ho mai nemmeno usato Flask in un vero progetto, sono più un utente di Django. Detto questo, non vedo davvero perché il caching sarebbe un problema, – BriceP