|
9 | 9 | 要让客户端记住并在每次请求时带上sessionid又有以下几种做法:
|
10 | 10 |
|
11 | 11 | 1. URL重写。所谓URL重写就是在URL中携带sessionid,例如:`http://www.example.com/index.html?sessionid=123456`,服务器通过获取sessionid参数的值来取到与之对应的session对象。
|
| 12 | + |
12 | 13 | 2. 隐藏域(隐式表单域)。在提交表单的时候,可以通过在表单中设置隐藏域向服务器发送额外的数据。例如:`<input type="hidden" name="sessionid" value="123456">`。
|
13 |
| -3. Cookie。Cookie是保存在浏览器临时文件中的数据,每次请求时,请求头中会携带本站点的Cookie到服务器,那么只要将sessionid写入cookie,下次请求时服务器就能够获得这个sessionid。 |
14 | 14 |
|
15 |
| -需要说明的是,在HTML5时代要想在浏览器中保存数据,除了使用Cookie之外,还可以使用新的本地存储API,包括localStorage、sessionStorage、IndexedDB等,如下图所示。 |
| 15 | +3. Cookie。Cookie是保存在浏览器临时文件中的数据,每次请求时,请求头中会携带本站点的cookie到服务器,那么只要将sessionid写入cookie,下次请求时服务器只要读取请求头中的cookie就能够获得这个sessionid,如下图所示: |
| 16 | + |
| 17 | +  |
| 18 | + |
| 19 | +需要说明的是,在HTML5时代要想在浏览器中保存数据,除了使用cookie之外,还可以使用新的本地存储API,包括localStorage、sessionStorage、IndexedDB等,如下图所示。 |
16 | 20 |
|
17 | 21 | 
|
18 | 22 |
|
19 |
| -### Django框架对Session的支持 |
| 23 | +### Django框架对session的支持 |
| 24 | + |
| 25 | +在创建Django项目时,默认的配置文件`settings.py`文件中已经激活了一个名为`SessionMiddleware`的中间件(关于中间件的知识我们在下一个章节做详细的讲解,这里只需要知道它的存在即可),因为这个中间件的存在,我们可以直接通过请求对象的`session`属性来操作会话对象。`session`属性是一个像字典一样可以读写数据的容器对象,因此我们可以使用“键值对”的方式来保留用户数据。与此同时,`SessionMiddleware`中间件还封装了对cookie的操作,在cookie中保存了sessionid,就如同我们之前描述的那样。 |
| 26 | + |
| 27 | +在默认情况下,Django将session的数据序列化后保存在关系型数据库中,在Django 1.6以后的版本中,默认的序列化数据的方式是JSON序列化,而在此之前一直使用Pickle序列化。JSON序列化和Pickle序列化的差别在于前者将对象序列化为字符串(字符形式),而后者将对象序列化为字节串(二进制形式),因为安全方面的原因,JSON序列化成为了目前Django框架默认序列化数据的方式,这就要求在我们保存在session中的数据必须是能够JSON序列化的,否则就会引发异常。还有一点需要说明的是,使用关系型数据库保存session中的数据在大多数时候并不是最好的选择,因为数据库可能会承受巨大的压力而成为系统性能的瓶颈,在后面的章节中我们会告诉大家如何将session的数据保存到缓存服务中。 |
| 28 | + |
| 29 | +我们继续完善之前的投票应用,前一个章节中我们实现了用户的登录和注册,下面我们首先完善登录时对验证码的检查。 |
| 30 | + |
| 31 | +```Python |
| 32 | +def get_captcha(request): |
| 33 | + """验证码""" |
| 34 | + captcha_text = random_captcha_text() |
| 35 | + request.session['captcha'] = captcha_text |
| 36 | + image_data = Captcha.instance().generate(captcha_text) |
| 37 | + return HttpResponse(image_data, content_type='image/png') |
| 38 | +``` |
| 39 | + |
| 40 | +注意上面代码中的第4行,我们将随机生成的验证码字符串保存到session中,稍后用户登录时,我们要将保存在session中的验证码字符串和用户输入的验证码字符串进行比对,如果用户输入了正确的验证码才能够执行后续的登录流程,代码如下所示。 |
| 41 | + |
| 42 | +```Python |
| 43 | +def login(request: HttpRequest): |
| 44 | + """登录""" |
| 45 | + hint = '' |
| 46 | + if request.method == 'POST': |
| 47 | + form = LoginForm(request.POST) |
| 48 | + if form.is_valid(): |
| 49 | + # 对验证码的正确性进行验证 |
| 50 | + captcha_from_user = form.cleaned_data['captcha'] |
| 51 | + captcha_from_sess = request.session.get('captcha', '') |
| 52 | + if captcha_from_sess.lower() != captcha_from_user.lower(): |
| 53 | + hint = '请输入正确的验证码' |
| 54 | + else: |
| 55 | + username = form.cleaned_data['username'] |
| 56 | + password = form.cleaned_data['password'] |
| 57 | + user = User.objects.filter(username=username, password=password).first() |
| 58 | + if user: |
| 59 | + # 登录成功后将用户编号和用户名保存在session中 |
| 60 | + request.session['no'] = user.no |
| 61 | + request.session['username'] = user.username |
| 62 | + return redirect('/') |
| 63 | + else: |
| 64 | + hint = '用户名或密码错误' |
| 65 | + else: |
| 66 | + hint = '请输入有效的登录信息' |
| 67 | + return render(request, 'login.html', {'hint': hint}) |
| 68 | +``` |
| 69 | + |
| 70 | +上面的代码中,我们设定了登录成功后会在session中保存用户的编号(`no`)和用户名(`username`),页面会重定向到首页。接下来我们可以稍微对首页的代码进行调整,在页面的右上角显示出登录用户的用户名。我们将这段代码单独写成了一个名为header.html的HTML文件,首页中可以通过在`<body>`标签中添加`{% include 'header.html' %}`来包含这个页面,代码如下所示。 |
| 71 | + |
| 72 | +```HTML |
| 73 | +<div class="user"> |
| 74 | + {% if request.session.no %} |
| 75 | + <span>{{ request.session.username }}</span> |
| 76 | + <a href="/vote/logout">注销</a> |
| 77 | + {% else %} |
| 78 | + <a href="/vote/login">登录</a> |
| 79 | + {% endif %} |
| 80 | + <a href="/vote/register">注册</a> |
| 81 | +</div> |
| 82 | +``` |
| 83 | + |
| 84 | +如果用户没有登录,页面会显示登录和注册的超链接;而用户登录成功后,页面上会显示用户名和注销的链接,注销链接对应的视图函数如下所示。 |
| 85 | + |
| 86 | +```Python |
| 87 | +def logout(request): |
| 88 | + """注销""" |
| 89 | + request.session.flush() |
| 90 | + return redirect('/') |
| 91 | +``` |
| 92 | + |
| 93 | +上面的代码通过session对象`flush`方法来销毁session,一方面清除了服务器上session对象保存的用户数据,一方面将保存在浏览器cookie中的sessionid删除掉,稍后我们会对如何读写cookie的操作加以说明。 |
| 94 | + |
| 95 | +我们可以通过项目使用的数据库中名为`django_session` 的表来找到所有的session,该表的结构如下所示: |
| 96 | + |
| 97 | +| session_key | session_data | expire_date | |
| 98 | +| -------------------------------- | ------------------------------- | -------------------------- | |
| 99 | +| c9g2gt5cxo0k2evykgpejhic5ae7bfpl | MmI4YzViYjJhOGMyMDJkY2M5Yzg3... | 2019-05-25 23:16:13.898522 | |
| 100 | + |
| 101 | +其中,第1列就是浏览器cookie中保存的sessionid;第2列是经过BASE64编码后的session中的数据,如果使用Python的`base64`对其进行解码,解码的过程和结果如下所示。 |
| 102 | + |
| 103 | +```Python |
| 104 | +>>> import base64 |
| 105 | +>>> base64.b64decode('MmI4YzViYjJhOGMyMDJkY2M5Yzg3ZWIyZGViZmUzYmYxNzdlNDdmZjp7ImNhcHRjaGEiOiJzS3d0Iiwibm8iOjEsInVzZXJuYW1lIjoiamFja2ZydWVkIn0=') |
| 106 | +'2b8c5bb2a8c202dcc9c87eb2debfe3bf177e47ff:{"captcha":"sKwt","no":1,"username":"jackfrued"}' |
| 107 | +``` |
| 108 | + |
| 109 | +第3列是session的过期时间,session过期后浏览器保存的cookie中的sessionid就会失效,但是数据库中的这条对应的记录仍然会存在,如果想清除过期的数据,可以使用下面的命令。 |
| 110 | + |
| 111 | +```Shell |
| 112 | +python manage.py clearsessions |
| 113 | +``` |
| 114 | + |
| 115 | +Django框架默认的session过期时间为两周(1209600秒),如果想修改这个时间,可以在项目的配置文件中添加如下所示的代码。 |
| 116 | + |
| 117 | +```Python |
| 118 | +# 配置会话的超时时间为1天(86400秒) |
| 119 | +SESSION_COOKIE_AGE = 86400 |
| 120 | +``` |
| 121 | + |
| 122 | +有很多对安全性要求较高的应用都必须在关闭浏览器窗口时让会话过期,不再保留用户的任何信息,如果希望在关闭浏览器窗口时就让会话过期(cookie中的sessionid失效),可以加入如下所示的配置。 |
| 123 | + |
| 124 | +```Python |
| 125 | +# 设置为True在关闭浏览器窗口时session就过期 |
| 126 | +SESSION_EXPIRE_AT_BROWSER_CLOSE = True |
| 127 | +``` |
| 128 | + |
| 129 | +如果不希望将session的数据保存在数据库中,可以将其放入缓存中,对应的配置如下所示,缓存的配置和使用我们在后面讲解。 |
| 130 | + |
| 131 | +```Python |
| 132 | +# 配置将会话对象放到缓存中存储 |
| 133 | +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' |
| 134 | +# 配置使用哪一组缓存来保存会话 |
| 135 | +SESSION_CACHE_ALIAS = 'default' |
| 136 | +``` |
20 | 137 |
|
0 commit comments