2012-01-19 52 views
21

Quiero restringir que los usuarios conectados solo tengan una sesión activa, es decir, si el usuario inicia sesión con un nuevo sessionid, la sesión anterior debe finalizar. me encontré con un montón de ayuda en ya SO: here y herePermitir solo una sola sesión activa por usuario en la aplicación Django

he implementado la solución de middleware, con un poco de comprobación adicional ...

class OnlyOneUserMiddleware(object): 
""" 
Middleware to ensure that a logged-in user only has one session active. 
Will kick out any previous session. 
""" 
def process_request(self, request): 
    if request.user.is_authenticated(): 
     try: 
      cur_session_key = request.user.get_profile().session_key 
      if cur_session_key and cur_session_key != request.session.session_key: 
       # Default handling... kick the old session... 
       Session.objects.get(session_key=cur_session_key).delete() 
      if not cur_session_key or cur_session_key != request.session.session_key: 
       p = request.user.get_profile() 
       p.session_key = request.session.session_key 
       p.save() 
     except ObjectDoesNotExist: 
      pass 

Hasta ahora, todo bien ... en el servidor de desarrollo de Django (manage.py runserver) funciona todo correctamente, activa la sesión anterior ...

... pero cuando se usa Apache (con mod_wsgi), ¡no funciona!

He tratado de encontrar ninguna información sobre esto, pero sin suerte hasta ahora ...

Lo más cerca que he encontrado es this, pero es tipo de problema 'opuesto' ...

Cualquier ayuda sería muy apreciada.

Edit: agregó una impresión de depuración antes de eliminar la Sesión ... He aquí un fragmento de error.log de Apache:

[Fri Jan 20 09:56:50 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a 
[Fri Jan 20 09:56:50 2012] [error] new key = ce4cfb672e6025edb8ffcd0cf2b4b8d1 
[Fri Jan 20 09:57:14 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a 
[Fri Jan 20 09:57:14 2012] [error] new key = 0815c56241ac21cf4b14b326f0aa7e24 

las dos primeras mentiras son de cuando entré con la primera sesión (Firefox)

los dos últimos son de cuando entré con la segunda sesión (cromo)

... resulta que el registro de sesión de edad no se borran ... ???

estoy corriendo contra el exacto la misma instancia PostgreSQL como lo hice con el devserver ...

Edit2: Resultó que era mi código con errores ... que falló cuando el nuevo session_key no era en la sesión nº ...

aquí está el código fijo ... la try..except está ahora en el lugar correcto

class OnlyOneUserMiddleware(object): 
    """ 
    Middleware to ensure that a logged-in user only has one session active. 
    Will kick out any previous session. 
    """ 
    def process_request(self, request): 
     if request.user.is_authenticated(): 
      cur_session_key = request.user.get_profile().session_key 
      if cur_session_key and cur_session_key != request.session.session_key: 
       # Default handling... kick the old session... 
       try: 
        s = Session.objects.get(session_key=cur_session_key) 
        s.delete() 
       except ObjectDoesNotExist: 
        pass 
      if not cur_session_key or cur_session_key != request.session.session_key: 
       p = request.user.get_profile() 
       p.session_key = request.session.session_key 
       p.save() 
+4

Cuando dices "no funciona", ¿qué es exactamente lo que no funciona? ¿Todavía ves la sesión anterior en la base de datos? Si pone una llamada de impresión/registro justo antes de la eliminación de 'Session', ¿ve que se ejecuta bajo' mod_wsgi'? – AdamKG

+0

@ AdamKG: ¡Gracias por apuntarme en la dirección correcta! –

+0

Si el motor de sesión es cache_db, creo que también necesitamos eliminar sessionkey del caché manualmente, ¿verdad? – Wesley

Respuesta

1

siempre puede utilizar este enfoque, aunque no se recomienda, funciona.

my_old_sessions = Session.objects.all() 
for row in my_old_sessions: 
    if row.get_decoded().get("_username") == request.user.username: 
     row.delete() 

Debería implementar el código anterior en su función de inicio de sesión() justo antes de autenticar al usuario.

Por supuesto, esto sólo funciona si tiene un método de función de inicio de sesión() que almacena los USUARIOS nombre de usuario en su sesión como sigue:

request.session["_username"] = request.user.username 

Si utiliza este enfoque sólo recuerda a vaciar su base de datos de todos sus sesiones antes de ejecutar su servidor después de realizar estos cambios, ya que generará errores de KeyLookUp.

+0

_nombre de usuario ya no está en get_decoded() de un objeto de sesión() (Django 1.8). Sin embargo, user_id es, por lo que 'row.get_decoded(). Get (" _ auth_user_id ") == request.user.pk' funcionaría. – guival

1

De hecho, hay muchas preguntas similares por todos lados, pero esta es mi solución.

Cuando un usuario inicia sesión sobre todas las sesiones activas y elimina las que tienen el mismo user.id.Para sitios web más pequeños, esto debería funcionar bien.

# __init__.py 
# Logs user out from all other sessions on login, django 1.8 

from django.contrib.sessions.models import Session 
from django.contrib.auth.signals import user_logged_in 
from django.db.models import Q 
from django.utils import timezone 

def limit_sessions(sender, user, request, **kwargs): 
    # this will be slow for sites with LOTS of active users 

    for session in Session.objects.filter(
     ~Q(session_key = request.session.session_key), 
     expire_date__gte = timezone.now() 
    ): 
     data = session.get_decoded() 
     if data.get('_auth_user_id', None) == str(user.id): 
      # found duplicate session, expire it 
      session.expire_date = timezone.now() 
      session.save() 

    return 

user_logged_in.connect(limit_sessions) 
+0

¿Ha comparado el rendimiento entre las dos formas? Quiero decir, a través de señales y middleware. Si usa middleware, todas las solicitudes deben pasar por handle_request. Si usa señales, aunque solo el evento de inicio de sesión golpea la función, pero se repetirá sobre la tabla de la sesión ... piense en el rendimiento, que es mejor ... – Wesley

+0

dije "para sitios web más pequeños" :) - es posible que desee almacene la clave como lo hace OP pero solo verifíquela al iniciar sesión como lo hago para una solución más eficaz – MarZab

+0

Sí, veo que lo mencionó para sitios web más pequeños :-) Solo quiero saber qué es mejor para sitios más grandes :-) – Wesley

0

Creo que, de alguna manera, las señales django.contrib.auth podrían ayudar aquí. Al iniciar sesión, invalida sesiones de usuarios anteriores.

+0

Si está Ya trabajando con una base de datos de usuario, esto significaría que todos los usuarios tendrían que cerrar sesión primero para que esto funcione. – guival

Cuestiones relacionadas