如何在Django中因不活跃而使会话过期?
我们的Django应用有以下会话管理需求。
- 当用户关闭浏览器时,会话会过期。
- 如果用户一段时间没有活动,会话也会过期。
- 当会话因不活动而过期时,能够检测到并向用户显示相应的消息。
- 在不活动期结束前几分钟提醒用户会话即将过期,并提供延长会话的选项。
- 如果用户在应用中进行长时间的业务操作,而没有向服务器发送请求,会话不能超时。
在阅读了相关文档、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 个回答
django-session-security 就是用来做这个的...
... 还有一个额外的要求:如果服务器没有回应,或者攻击者断开了网络连接,那么会话应该自动过期。
声明:我在维护这个应用程序。不过我已经关注这个话题很久很久了 :)
这里有个主意……可以通过设置 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']
设置为当前时间就可以了。
显然,这段代码只是个开始……但它应该能让你走上正确的道路。
我刚开始接触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浏览器上测试过。
- 当我关闭浏览器时,会话就过期了,即使设置了SESSION_COOKIE_AGE。
- 只有当我闲置超过10秒时,会话才会过期。多亏了SESSION_SAVE_EVERY_REQUEST,每当你发起新请求时,它会保存会话并更新超时时间。
要改变这种默认行为,可以把SESSION_SAVE_EVERY_REQUEST设置为True。当设置为True时,Django会在每次请求时将会话保存到数据库。
注意,只有在会话被创建或修改时,才会发送会话cookie。如果SESSION_SAVE_EVERY_REQUEST为True,所有请求都会发送会话cookie。
同样,每次发送会话cookie时,cookie的过期时间也会更新。
我留下这个回答是希望像我一样刚接触Django的人,不用花太多时间去寻找解决方案。