如何在Django中因不活跃而使会话过期?

116 投票
7 回答
69353 浏览
提问于 2025-04-15 23:51

我们的Django应用有以下会话管理需求。

  1. 当用户关闭浏览器时,会话会过期。
  2. 如果用户一段时间没有活动,会话也会过期。
  3. 当会话因不活动而过期时,能够检测到并向用户显示相应的消息。
  4. 在不活动期结束前几分钟提醒用户会话即将过期,并提供延长会话的选项。
  5. 如果用户在应用中进行长时间的业务操作,而没有向服务器发送请求,会话不能超时。

在阅读了相关文档、Django代码和一些博客文章后,我想出了以下实现方案。

需求1
这个需求很简单,只需将SESSION_EXPIRE_AT_BROWSER_CLOSE设置为True即可。

需求2
我看到有一些建议使用SESSION_COOKIE_AGE来设置会话过期时间。但这种方法有以下问题。

  • 即使用户正在积极使用应用,会话也会在SESSION_COOKIE_AGE结束时过期。(可以通过在每个请求中使用自定义中间件将会话过期时间设置为SESSION_COOKIE_AGE,或者通过将SESSION_SAVE_EVERY_REQUEST设置为true在每个请求中保存会话来防止。但由于使用SESSION_COOKIE_AGE,下面的问题是无法避免的。)

  • 由于cookie的工作方式,SESSION_EXPIRE_AT_BROWSER_CLOSE和SESSION_COOKIE_AGE是互斥的,也就是说,cookie要么在浏览器关闭时过期,要么在指定的过期时间过期。如果使用SESSION_COOKIE_AGE,而用户在cookie过期之前关闭了浏览器,cookie会被保留,重新打开浏览器后,用户(或其他人)可以不重新认证就进入系统。

  • Django仅依赖cookie是否存在来判断会话是否活跃。它并不会检查与会话一起存储的会话过期日期。

可以使用以下方法来实现这个需求,并解决上述问题。

  • 不要设置SESSION_COOKIE_AGE。
  • 在每个请求中将会话的过期时间设置为“当前时间 + 不活动时间”。
  • 在SessionMiddleware中重写process_request,检查会话是否过期。如果过期,则丢弃会话。

需求3
当我们检测到会话已过期时(在上面的自定义SessionMiddleware中),在请求中设置一个属性来指示会话过期。这个属性可以用来向用户显示相应的消息。

需求4
使用JavaScript检测用户的不活动,提供警告并给出延长会话的选项。如果用户希望延长,会向服务器发送一个保持活动的信号,以延长会话。

需求5
使用JavaScript检测用户的活动(在长时间的业务操作中),并向服务器发送保持活动的信号,以防止会话过期。


以上的实现方案看起来很复杂,我在想是否有更简单的方法(特别是对于需求2)。

任何见解都将非常感谢。

7 个回答

28

django-session-security 就是用来做这个的...

... 还有一个额外的要求:如果服务器没有回应,或者攻击者断开了网络连接,那么会话应该自动过期。

声明:我在维护这个应用程序。不过我已经关注这个话题很久很久了 :)

49

这里有个主意……可以通过设置 SESSION_EXPIRE_AT_BROWSER_CLOSE 来让会话在浏览器关闭时过期。然后在每次请求时,像这样在会话中记录一个时间戳。

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

接着,添加一个中间件来检测会话是否过期。类似这样的代码应该能处理整个过程……

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

然后你只需要创建一些网址和视图,来返回与会话过期相关的数据给 ajax 调用。

当用户选择“续期”会话时,你只需要把 request.session['last_activity'] 设置为当前时间就可以了。

显然,这段代码只是个开始……但它应该能让你走上正确的道路。

56

我刚开始接触Django。

我想让用户在关闭浏览器或者长时间不活动(闲置超时)时,自动让会话过期。我在网上搜索这个问题时,发现了一个StackOverflow的问题。感谢那个很棒的回答,我查了一些资料,了解了Django中间件在请求和响应过程中的工作原理,这对我帮助很大。

我准备按照这里的最佳回答,把自定义中间件应用到我的代码中。但我还是有点怀疑,因为这里的最佳回答是2011年编辑的。我花了一些时间从最近的搜索结果中寻找,找到了一个简单的方法。

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

我只在Chrome浏览器上测试过。

  1. 当我关闭浏览器时,会话就过期了,即使设置了SESSION_COOKIE_AGE。
  2. 只有当我闲置超过10秒时,会话才会过期。多亏了SESSION_SAVE_EVERY_REQUEST,每当你发起新请求时,它会保存会话并更新超时时间。

要改变这种默认行为,可以把SESSION_SAVE_EVERY_REQUEST设置为True。当设置为True时,Django会在每次请求时将会话保存到数据库。

注意,只有在会话被创建或修改时,才会发送会话cookie。如果SESSION_SAVE_EVERY_REQUEST为True,所有请求都会发送会话cookie。

同样,每次发送会话cookie时,cookie的过期时间也会更新。

django手册1.10

我留下这个回答是希望像我一样刚接触Django的人,不用花太多时间去寻找解决方案。

撰写回答