基于How To Tango With Django 1.9的重新实践(10)——Cookies and Sessions

在本章中我们将要介绍sessions和cookies,它们两个在现代wen应用中有着至关重要的作用.在上一章,Django框架使用sessions和cookies来处理用户登录和注销功能(都在背后运行).这里我们将要探索cookies的其他用途.

10.1 Cookies,无处不在的Cookies!

每当对网站做出请求时,web服务器返回所请求的页面的内容。此外,一个或多个cookie也可以作为请求的一部分被发送。将cookie视为从服务器发送到客户端的一小段信息。当要发送请求时,客户端检查以查看是否在客户端上存在与服务器地址匹配的任何Cookie。如果是,请求中包含它们。然后,服务器可以将Cookie解释为请求的上下文的一部分,并生成适合的响应。

例如,您可以使用特定用户名和密码登录网站。当您进行身份验证后,可能会在浏览器中返回包含您的用户名的Cookie,表明您现在已登录到该网站。每次请求时,此信息都会传回到您的登录信息用于呈现相应页面的服务器,可能包括您在网页上特定位置的用户名。你的会话不能永远持续,但是cookie 必须在某个时间点过期 - 它们不能是无限长度。包含敏感信息的Web应用程序可能只在几分钟不活动后就会过期。具有琐碎信息的不同的Web应用程序可能在最后一次交互后半小时或甚至几周后到期。

术语cookie实际上不是从你吃的食物中衍生出来的,但是从术语magic cookie,程序接收和发送的数据包不变。

以Cookie形式传递信息可能会在Web应用程序的设计中产生潜在的安全漏洞。这就是为什么Web应用程序的开发人员在使用Cookie时需要非常小心的原因。当使用cookie时,设计师必须总是问自己:你想要存储为cookie的信息真的需要发送并存储在客户机器上吗?在许多情况下,有更安全的解决方案。例如,在电子商务网站上将用户的信用卡号作为Cookie传递会非常不安全。如果用户的计算机被盗用了怎么办?恶意程序可能会接受cookie。从那里,黑客会有他或她的信用卡号码 - 所有因为你的Web应用程序的设计从根本上有缺陷。

11.2 Sessions和无状态协议

所有的浏览器和服务器之间的交互都会通过HTTP协议.通过第8章简单的接触我们知道HTTP是无状态协议.这就意味着每次客户端请求(HTTPGET)或者发送(HTTPPOST)资源给服务器都必须新建立一个连接(一个TCP连接).

因为客户端和服务器没有持续的连接,两端的软件不能仅仅通过单独的连接保持会话状态.例如,客户端每次都需要告诉服务器谁在这个主机上登录了这个web应用.这就是用户和服务器之间的会话,这也是session的基本 - a semi-permanent exchange of information.作为一个无状态协议,HTTP需要保持会话状态非常的困难 - 但是很走运我们可以使用几种技术来绕过这个问题.

最常用的一种保持状态的方法就是使用session ID,和cookies一样存储在客户端电脑.session ID可以认为是一种令牌(一大串字符串),它能够唯一标识特定web应用里的会话.和cookies存储许多不同种类的数据不一样(像用户名,名字,密码),它只存储session ID,并且映射到web服务器的一个数据结构.在这个数据结构里,你可以存储任何你需要的信息.这对于用户来说是更安全的存储方法.用这种方法,这些数据不会被一个不安全的客户端和连接所监听.

如果你的浏览器支持cookies,当你访问所有的网站时都会创建一个新的会话.你可以自己查看下,如图所示.在Google Chrome的开发者工具中,你可以查看到web服务器发送给你的cookies.在下图中,你可以观察到选中的sessionidcookie.这个cookie包含一系列的字母和数字,它可以使Django唯一标识一个会话.到现在,所有的session细节都表述完了 - 但是服务器端还没讲.

session ID也可以不存储在cookies中.传统的PHP应用会把它们做成一个请求字符串或是URL的一部分来请求资源.如果你偶然间访问像http://www.site.com/index.php?sessid=omgPhPwtfIsThisIdDoingHere332i942394这样的URL,很可能是服务器唯一识别你的标识.很有趣吧!

11.3 在Django里设置Sessions

尽管我们已经设置好并且正确工作,但是如果学会Django的模块提供哪些工作就会更好了.关于sessions方面Django提供了middleware来提供session功能.

为了检查一切都设置妥当,打开Django项目里的settings.py文件.找到MIDDLEWARE_CLASSES元组.你可以在元组中看到django.contrib.sessions.middleware.SessionMiddleware模块 - 如果没有看到现在就加进去.正式这个SessionMiddleware中间件能够创建唯一的sessionidcookies.

SessionMiddleware可以灵活的使用不同的方法来存储session信息.你可以采取很多方法储存 - 存在文件,数据库甚至在cache中.最直接的方法是使用django.contrib.sessions应用把session信息存储到Django模型/数据库中(详细的说是django.contrib.sessions.models.Session模型).使用这种方法,你必须首先确保在Django项目的settings.py文件里django.contrib.sessions保存在INSTALLED_APPS元组里.如果你现在要添加应用,你需要使用迁移命令来更新数据库.

11.4 基于cookie的session

我们现在测试我们的浏览器是否支持cookies.现代浏览器都支持cookies,你可以查看你浏览器的cookies信息.如果你的浏览器安全等级设定的非常高,一些特定的cookies会被阻塞.查看浏览器文档获取更多信息,设定使用cookies.

11.4.1 测试Cookie功能

为了测试cookies,你可以使用Django的request对象提供的便捷方法.其中set_test_cookie(),test_cookie_worked()delete_test_cookie()方法对我们比较有用.在一个视图里,你需要设置一个cookie.在另一个视图你需要检查cookie是否存在.两个不同的视图都要请求cookies,是因为你要查看是否客户端接收了来自服务器的cookie.

我们将会使用前面创建的两个视图,index()和about()。我们将使用Django开发服务器的终端输出来验证Cookie是否正常工作,而不是在页面上显示任何内容。

在Rango的views.py文件中,找到您的index()视图。将以下行添加到视图。为了确保执行行,请确保将其作为视图的第一行。

request.session.set_test_cookie()

在register()视图顶部加入下面3行代码 - 同样是为了保证它们能被执行.

if request.session.test_cookie_worked():
    print ">>>> TEST COOKIE WORKED!"
    request.session.delete_test_cookie()

更改完以后运行Django服务并打开Rango首页,http://127.0.0.1:8000/rango/.页面加载完后,前往注册页面.当注册页面加载后,你将会和下图一样在终端里看到>>>> TEST COOKIE WORKED!.

这里写图片描述

你可以删除增加的代码 - 我们只是拿它用来验证以下cookies

11.5 客户端Cookie:站点计数器示例

现在我们已经知道cookies是如何工作的,让我们实现一个简单的网站访问计数.首先我们需要创建两个cookies:一个用来追踪用户访问Rango网站次数,另一个用来追踪上一次登录的时间.保持追踪用户上一次的访问时间和日期将允许我们每天只能增长一次访问次数.

假设用户访问的Rango页面一般为首页.打开rango/view.py并修改index()视图如下.

注意,它在技术上不是一个视图,因为它不返回一个response对象 - 它只是一个帮助函数。

def index(request):
    '''
    #此段代码用于验证cookies是否正常工作
    request.session.set_test_cookie()
    '''

    #使用order_by对likes进行降序排列,取前五个保存到category_list
    category_list = Category.objects.order_by('-likes')[:5]
    page_list = Page.objects.order_by('-views')[:5]

    context_dict = {'categories':category_list,'pages':page_list}

    #尽早获取我们的Response对象,以便我们可以添加cookie信息。
    response = render(request,'rango/index.html',context_dict)

    #调用帮助函数来处理cookie
    visitor_cookie_handle(request,response)

    #将响应返回给用户,更新所有需要更改的Cookie。
    return response
#如果你想出去测试此代码,而无需等待一天,改days到seconds。这样,访问计数器可以每秒更新,而不是每天更新。
def visitor_cookie_handle(request,response):
    '''
    获取网站的访问次数,我们使用COOKIES.get()函数获取访问Cookie。
    如果cookie存在,则返回的值将转换为整数。
    如果cookie不存在,则使用默认值1。
    注意返回的所有的cookie值都是字符串;不要认为cookie保存的是数字就会返回整型.你需要自己把它们转化成正确的形式.
    如果这个cookie不存在,你需要使用response对象的set_cookie()方法来创建cookie.
    这个方法包含两个值,cookie名(字符串)和cookie值.不管你输入的是什么形式 - 它都会自动转化成字符串.
    '''
    visits = int(request.COOKIES.get('visits','1'))

    #获取cookie的值
    last_visit_cookie = request.COOKIES.get('last_visit',str(datetime.now()))

    #把值转换为Python date/time 对象
    last_visit_time = datetime.strptime(last_visit_cookie[:-7],'%Y-%m-%d %H:%M:%S')

    #如果自从上次登录超过一天
    if (datetime.now()-last_visit_time).days > 0:
        visits = visits + 1

        #现在更新上次访问的cookie,我们已经更新了count
        response.set_cookie('last_visit',str(datetime.now()))

    else:
        visits = 1

        #设置上次访问的cookie
        response.set_cookie('last_visit',last_visit_cookie)

    #更新/设置visits的cookie
    response.set_cookie('visit',visits)

读下来这段代码你将会看到大部分的代码都是在处理当前日期和时间.所以在这里你需要在views.py文件的头部加入Python的datetime模块.

from datatime import datetime

确保引进了datetime模块里的datetime对象.

在代码中我们检查last_visit是否存在.如果存在我们就使用request.COOKIES['cookie_name']语法来获取它的值,这里的request就是request对象的名字,cookie_name就是你希望跟踪的cookie名.注意返回的所有的cookie值都是字符串;不要认为cookie保存的是数字就会返回整型.你需要自己把它们转化成正确的形式.如果这个cookie不存在,你需要使用response对象的set_cookie()方法来创建cookie.这个方法包含两个值,cookie名(字符串)和cookie值.不管你输入的是什么形式 - 它都会自动转化成字符串.

这里写图片描述

您可能会注意到,visits刷新网络浏览器时Cookie不会递增。为什么?我们在上面提供的示例代码仅在用户重新访问Rango主页后至少增加一天。这是一个不可接受的时间等待测试
- 所以为什么不临时更改延迟到更短的时间段?在更新的index视图中,找到以下行。

if (datetime.now() - last_visit_time).days > 0:

我们可以轻松地更改此行以比较 访问之间的秒数。在下面的示例中,我们检查用户是否至少五秒前访问过。

if (datetime.now() - last_visit_time).seconds > 5:

这意味着您只需等待五秒钟即可查看visitsCookie增量,而不是一整天。当您的代码运行良好时,您可以将比较恢复为原始的每天时间段。

能够使用-运算符找到时间之间的差异是Python提供的许多令人敬畏的功能之一。当减去时间时,timedelta返回一个对象,它提供了
我们在上面的代码片段中使用的属性days和seconds属性。你可以查看官方的Python文档
,获取关于这种类型对象的更多信息,以及它提供的其他属性。

11.6 Session数据

在前一个例子中我们使用了客户端的cookies.然而,另一种更安全的方法是把session信息存储在服务器端.我们可以使用存储在客户端的session ID cookie作为解锁数据的钥匙.

使用基于cookie的session你需要完成以下几步.

  1. 确保settings.py文件的MIDDLEWARE_CLASSES包含django.contrib.sessions.middleware.SessionMiddleware.
  2. 配置session后台.确定django.contrib.sessions在你settings.py文件的INSTALLED_APPS里.如果没有则添加并且运行数据库迁移命令python
    manage.py migrate.
  3. 假设使用数据库作为后台,但是你也可以设置成其他(比如cache).参见 official Django Documentation on Sessions for other backend configurations.

而不是直接存储在请求(因此在客户端的机器上),您可以通过该方法访问服务器端数据,request.session.get()并存储它们request.session[]。请注意,会话ID cookie仍然用于记住客户端的计算机(因此技术上存在浏览器端Cookie)。但是,所有的用户/会话数据都存储在服务器端。Django的会话中间件处理客户端cookie和用户/会话数据的存储。

要使用服务器端数据,我们需要重构我们目前为止编写的代码。首先,我们需要更新visitor_cookie_handler()函数,以便它访问服务器端的Cookie。我们可以通过调用来做到这一点request.session.get(),并将它们存储在字典中request.session[]。为了帮助我们,我们做了一个帮助函数调用get_server_side_cookie(),请求一个cookie的请求。如果cookie在会话数据中,则返回其值。否则,将返回默认值。

由于所有的cookie都存储在服务器端,我们不会直接更改响应。因此,我们可以从visitor_cookie_handler()函数定义中删除response.

11.7 Browser-Length and Persistent Sessions

当你使用cookies时你可以使用Django的session框架来设置browser-lenght sessions或者persistent sessions.这两个名字的解释如下:

  • browser-length sessions是当浏览器关闭时截至.
  • persistent sessions可以随你的选择而结束.它可以是一个小时甚至是一个月.

默认的browser-length sessions是被关闭的.你可以通过设置Django的settings.py来开启它(增加SESSION_EXPIRE_AT_BROWSER_CLOSE并设置为true).

persistent session默认是开启的,可以设置SESSION_EXPIRE_AT_BROWSER_CLOSEFalse进行开启.persistent sessions有一个额外的设置SESSION_COOKIE_AGE,它可以允许你设置cookie的存活时间.这个值是一个整型,代表着cookie能存活的秒数.例如修改为1209600意味着网页的cookies将会在两周后过期.

11.8 清除Sessions数据库

Session cookies是不断累积的.所以如果你是使用数据库作为后台你需要定期的清除存储的cookies.可以用python manage.py clearsessions命令来实现.Django文档里建议每天运行一次.

11.9 基本的注意事项和流程

在Django应用中使用cookies,有一些事情你需要注意:

  • 首先,考虑你的web应用需要什么类型的cookies.你保存的信息需要持续存储还是在会话结束后就截至?
  • 对你使用cookies储存的信息要格外小心.存储在cookies里的信息同样会呈现在用户的电脑上.这样做的风险非常的大:你不会知道用户的电脑是多么的危险.如果保存一些敏感的信息还是存在服务器端吧.
  • 另一个致命的是用户可以设置他的浏览器级别非常的高,这将会阻止你的cookies.一旦你的cookies被阻拦,你的网站功能就会异常.你必须想到这一点.你无法控制用户浏览器.

如果你的情况适合客户端cookies,你可以按照如下步骤进行:

  1. 首先检查你希望的cookie是否存在.可以用检查request参数实现.request.COOKIES.has_key('<cookie_name>')函数会返回一个布尔值来告诉我们是否有一个叫<cookie_name>的cookie存在.
  2. 如果这个cookie存在,你可以通过request.COOKIES[]来获取.COOKIES属性是一个字典,你可以把你希望获取的cookie名写入方括号中.记住所有的cookies都返回为字符串.因此你必须把它们转化成正确的类型.
  3. 如果cookie不存在或者你希望更新cookie.你可以使用response.set_cookie('<cookie_name>',value)函数来实现,这里的两个参数分别是cookie的名字和他们的值.

如果你需要更安全的cookies,那么就需要使用基于cookies的session:

  1. 确保settings.py中的MIDDLEWARE_CLASSES包含django.contrib.sessions.middleware.SessionMiddleware.
  2. 设置你的session后台SESSION_ENGINE.查看 official Django Documentation on Sessions 获取不同后台的不同设置.
  3. 通过requests.sessions.get()来获取存在的cookie.
  4. 通过requests.session['<cookie_name>']更改或设置cookie.

11.10Exercises

  • 检查您的Cookie是服务器端。清除浏览器的缓存和Cookie,然后检查以确保您在浏览器中看不到last_visit和visits变量。请注意,您仍然会看到该sessionidCookie。Django使用这个cookie在数据库中查找会话,它存储了该会话的所有服务器端cookie。

这里写图片描述

  • 更新About页面的视图和模板,告诉访问者他们已经登陆这个站点的次数。记得调用visitor_cookie_handler()在你试图得到访客的cookie从request.session字典,否则如果cookie没有设置,它将显示一个错误。

这里写图片描述

这里写图片描述

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值