Como expirar uma sessão por inatividade no Django?

93

Nosso aplicativo Django possui os seguintes requisitos de gerenciamento de sessão.

  1. As sessões expiram quando o usuário fecha o navegador.
  2. As sessões expiram após um período de inatividade.
  3. Detecta quando uma sessão expira devido à inatividade e exibe a mensagem apropriada para o usuário.
  4. Avise os usuários sobre a expiração de uma sessão iminente alguns minutos antes do final do período de inatividade. Junto com o aviso, forneça aos usuários a opção de estender sua sessão.
  5. Se o usuário estiver trabalhando em uma longa atividade de negócios dentro do aplicativo que não envolve o envio de solicitações ao servidor, a sessão não deve expirar.

Depois de ler a documentação, o código do Django e alguns posts de blog relacionados a isso, eu vim com a seguinte abordagem de implementação.

Requisito 1
Este requisito é facilmente implementado definindo SESSION_EXPIRE_AT_BROWSER_CLOSE como True.

Requisito 2
Eu vi algumas recomendações para usar SESSION_COOKIE_AGE para definir o período de expiração da sessão. Mas esse método tem os seguintes problemas.

  • A sessão sempre expira no final de SESSION_COOKIE_AGE, mesmo se o usuário estiver usando ativamente o aplicativo. (Isso pode ser evitado configurando a expiração da sessão para SESSION_COOKIE_AGE em cada solicitação usando um middleware personalizado ou salvando a sessão em cada solicitação configurando SESSION_SAVE_EVERY_REQUEST como verdadeiro. Mas o próximo problema é inevitável devido ao uso de SESSION_COOKIE_AGE.)

  • Devido à forma como os cookies funcionam, SESSION_EXPIRE_AT_BROWSER_CLOSE e SESSION_COOKIE_AGE são mutuamente exclusivos, ou seja, o cookie expira no fechamento do navegador ou no tempo de expiração especificado. Se SESSION_COOKIE_AGE for usado e o usuário fechar o navegador antes que o cookie expire, o cookie é retido e a reabertura do navegador permitirá que o usuário (ou qualquer outra pessoa) entre no sistema sem ser reautenticado.

  • Django confia apenas na presença do cookie para determinar se a sessão está ativa. Ele não verifica a data de expiração da sessão armazenada com a sessão.

O método a seguir pode ser usado para implementar esse requisito e solucionar os problemas mencionados acima.

  • Não defina SESSION_COOKIE_AGE.
  • Defina a data de expiração da sessão como 'hora atual + período de inatividade' em cada solicitação.
  • Substitua process_request em SessionMiddleware e verifique a expiração da sessão. Descarte a sessão se ela tiver expirado.

Requisito 3
Quando detectamos que a sessão expirou (no SessionMiddleware personalizado acima), defina um atributo na solicitação para indicar a expiração da sessão. Este atributo pode ser usado para exibir uma mensagem apropriada para o usuário.

Requisito 4
Use JavaScript para detectar a inatividade do usuário, fornecer o aviso e também uma opção para estender a sessão. Se o usuário deseja estender, envie um pulso keep alive ao servidor para estender a sessão.

Requisito 5
Use JavaScript para detectar a atividade do usuário (durante a longa operação de negócios) e enviar pulsos de manutenção ao servidor para evitar que a sessão expire.


A abordagem de implementação acima parece muito elaborada e eu queria saber se poderia haver um método mais simples (especialmente para o Requisito 2).

Todos os insights serão muito apreciados.

Akbar ibrahim
fonte
3
+1 para fornecer uma solução detalhada
Don
Existe um middleware que pode fazer o que você precisa. no github e no pypi
gbutler
1
"Devido à forma como os cookies funcionam, SESSION_EXPIRE_AT_BROWSER_CLOSE e SESSION_COOKIE_AGE são mutuamente exclusivos, ou seja, o cookie expira no fechamento do navegador ou no tempo de expiração especificado. Se SESSION_COOKIE_AGE for usado e o usuário fechar o navegador antes que o cookie expire, o cookie é retido e reaberto o navegador permitirá que o usuário (ou qualquer outra pessoa) entre no sistema sem ser autenticado novamente. " Corrija-me se eu estiver errado, mas isso não parece mais ser verdade nas versões mais recentes do Django? (1.5+ pelo menos)
Botond Béres
1
"Django confia apenas na presença do cookie para determinar se a sessão está ativa. Ele não verifica a data de expiração da sessão armazenada com a sessão." Isso não é mais verdade .
knaperek

Respostas:

44

Aqui está uma ideia ... Expire a sessão no navegador feche com a SESSION_EXPIRE_AT_BROWSER_CLOSEconfiguração. Em seguida, defina um carimbo de data / hora na sessão em cada solicitação.

request.session['last_activity'] = datetime.now()

e adicione um middleware para detectar se a sessão expirou. algo assim deve lidar com todo o processo ...

from datetime import datetime
from django.http import HttpResponseRedirect

class SessionExpiredMiddleware:
    def process_request(request):
        last_activity = request.session['last_activity']
        now = datetime.now()

        if (now - last_activity).minutes > 10:
            # Do logout / expire session
            # and then...
            return HttpResponseRedirect("LOGIN_PAGE_URL")

        if not request.is_ajax():
            # don't set this for ajax requests or else your
            # expired session checks will keep the session from
            # expiring :)
            request.session['last_activity'] = now

Então você só precisa fazer algumas urls e visualizações para retornar dados relevantes para as chamadas ajax sobre a expiração da sessão.

quando o usuário opta por "renovar" a sessão, por assim dizer, tudo o que você precisa fazer é definir requeset.session['last_activity']a hora atual novamente

Obviamente, este código é apenas um começo ... mas ele deve colocá-lo no caminho certo

Jiaaro
fonte
Estou apenas sendo cético aqui, mas não acho que if not request.is_ajax()seja completamente seguro. Alguém que retém a sessão de pré-expiração não pode falsificar / enviar uma chamada ajax e manter a sessão em andamento?
notbad.jpeg
2
@ notbad.jpeg: em geral, "activity" é facilmente falsificável. Alguém que controla a sessão e continua enviando solicitações está apenas ativo.
RemcoGerlich de
Esta é uma ótima resposta. Middleware é uma ferramenta muito subutilizada no desenvolvimento do Django.
Jamie Counsell
30

Eu sou muito novo no uso do Django.

Eu queria fazer a sessão expirar se o usuário logado fechasse o navegador ou estivesse inativo (tempo limite de inatividade) por algum tempo. Quando eu pesquisei no Google para descobrir, esta questão SOF surgiu primeiro. Graças à boa resposta, pesquisei recursos para entender como os middlewares funcionam durante o ciclo de solicitação / resposta no Django. Foi muito útil.

Eu estava prestes a aplicar o middleware personalizado em meu código, seguindo a principal resposta aqui. Mas eu ainda estava um pouco desconfiado porque a melhor resposta aqui foi editada em 2011. Eu levei mais tempo para pesquisar um pouco no resultado de pesquisa recente e encontrei uma maneira simples.

SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 10 # set just 10 seconds to test
SESSION_SAVE_EVERY_REQUEST = True

Eu não verifiquei outros navegadores, mas o Chrome. 1. Uma sessão expirou quando fechei um navegador, mesmo se SESSION_COOKIE_AGE definido. 2. Somente quando fiquei inativo por mais de 10 segundos, Uma sessão expirou. Graças a SESSION_SAVE_EVERY_REQUEST, sempre que ocorrer nova solicitação, ele salva a sessão e atualiza o tempo limite para expirar

Para alterar esse comportamento padrão, defina a configuração SESSION_SAVE_EVERY_REQUEST como True. Quando definido como True, o Django salvará a sessão no banco de dados a cada solicitação.

Observe que o cookie de sessão é enviado apenas quando uma sessão é criada ou modificada. Se SESSION_SAVE_EVERY_REQUEST for True, o cookie de sessão será enviado em cada solicitação.

Da mesma forma, a parte expirada de um cookie de sessão é atualizada cada vez que o cookie de sessão é enviado.

django manual 1.10

Eu apenas deixo resposta para que algumas pessoas que são meio novas no Django como eu não percam muito tempo para encontrar uma solução como eu fiz.

Jayground
fonte
26

django-session-security faz exatamente isso ...

... com um requisito adicional: se o servidor não responder ou um invasor desconectar a conexão com a Internet: ela deve expirar de qualquer maneira.

Disclamer: Eu mantenho este aplicativo. Mas eu tenho assistido a este tópico por muito, muito tempo :)

jpic
fonte
1
aplicativo legal - bem projetado e bem construído. código bom e limpo ... obrigado.
nicorellius
Se o usuário fechar o navegador ou a guia (sem fazer logout) ao sair, isso ainda força o usuário a fazer o logout? Ele lida com essa condição?
Mehmet Kagan Kayaalp
Isso seria controlado pela expiração do cookie de sessão HTTP puro, não é?
Jpic
10

Uma maneira fácil de satisfazer seu segundo requisito seria definir o valor SESSION_COOKIE_AGE em settings.py para uma quantidade adequada de segundos. Por exemplo:

SESSION_COOKIE_AGE = 600      #10 minutes.

No entanto, ao fazer isso, a sessão irá expirar após 10 minutos, independentemente de o usuário exibir ou não alguma atividade. Para lidar com esse problema, o tempo de expiração pode ser renovado automaticamente (por mais 10 minutos extras) toda vez que o usuário realizar qualquer tipo de solicitação com a seguinte frase:

request.session.set_expiry(request.session.get_expiry_age())
Fernando martin
fonte
2
SESSION_COOKIE_AGE = 600 Isso estenderá a idade das sessões a cada nova solicitação de página ou atualização de página
Aseem
1
Confirmo que apenas configurar SESSION_COOKIE_AGEé suficiente e que qualquer solicitação (envio do cookie de sessão) irá atualizar automaticamente a expiração do cookie de sessão.
bruno desthuilliers de
3

você também pode usar o stackoverflow em funções

SESSION_SAVE_EVERY_REQUEST = True
Mexekanez
fonte
Totalmente não relacionado - isso é para forçar um salvamento da sessão (não "atualização do cookie da sessão") em cada solicitação sem verificar se a sessão foi modificada.
bruno desthuilliers
3

Na primeira solicitação, você pode definir a expiração da sessão como

self.request.session['access_key'] = access_key
self.request.session['access_token'] = access_token
self.request.session.set_expiry(set_age) #in seconds 

E ao usar access_key e token,

try:
    key = self.request.session['access_key']
except KeyError:
    age = self.request.session.get_expiry_age()
    if age > set_age:
        #redirect to login page
tilaprimera
fonte