Django组件之Cookie&Session

一 会话跟踪技术

1.1 什么是会话跟踪技术

我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如:你给 10086 打个电话,你就是客户端,而 10086 服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向 10086 发出多个请求,那么这多个请求都在一个会话中。在JavaWeb 中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。在一个会话的多个请求中共享数据,这就是会话跟踪技术。

例如在一个会话中的请求如下:请求银行主页

  • 请求登录(请求参数是用户名和密码);
  • 请求转账(请求参数与转账相关的数据);
  • 请求信用卡还款(请求参数与还款相关的数据)

在上面的会话中当前用户信息必须在这个会话中是共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款!这就说明我们必须在一个会话过程中有共享数据的能力。

1.2 会话路径技术使用 Cookie 或 session 完成

我们知道 HTTP 协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但 HTTP 协议中可以使用 Cookie 来完成会话跟踪!在 Web 开发中,使用 session 来完成会话跟踪, session 底层依赖 Cookie 技术。

二 Cookie

2.1 Cookie简介

  • 保存在客户端浏览器上的键值对
  • 服务器可以向用户浏览器端写Cookie
  • 客户端每次发送请求时,会携带Cookie到服务端
  • 主要应用在用户登录验证、投票

image

注意:客户端浏览器不要将cookie禁止,否则服务端无法设置客户端cookie数据,导致登录异常。

2.2 Cookie规范

  • Cookie 大小上限为 4KB
  • 一个服务器最多在客户端浏览器上保存 20 个 Cookie
  • 一个浏览器最多保存 300 个 Cookie

上面的数据只是HTTP 的 Cookie 规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对 Cookie 规范 扩展 了一些,例如每个 Cookie 的大小为 8KB ,最多可保存 500 个 Cookie 等!但也不会出现把你硬盘占满的可能!

注意:不同浏览器之间是不共享Cookie 的。也就是说在你使用 IE 访问服务器时,服务器会把 Cookie 发给 IE ,然后由IE 保存起来,当你在使用 FireFox 访问服务器时,不可能把 IE 保存的 Cookie 发送给服务器。

2.3 Cookie与HTTP头

Cookie是通过 HTTP 请求和响应头在客户端和服务器端传递的:

  • Cookie :请求头,客户端发送给服务器端
  • 格式: Cookie: a=A; b=B; c=C 。即多个 Cookie 用分号离开;
  • Set Cookie :响应头,服务器端发送给客户端
  • 一个 Cookie 对象一个 Set Cookie。Set Cookie: a=A Set Cookie: b=B Set Cookie: c=C

2.4 Cookie的覆盖

如果服务器端发送重复的 Cookie 那么会覆盖原有的 Cookie ,例如:客 户端的第一个请求服务器端发送的 Cookie 是:Set Cookie: a=A ;第二请求服务器端发送的是 Set Cookie: a=AA ,那么客户端只留下一个 Cookie ,即 a=AA 。

2.5 Cookie操作

2.5.1 设置cookie

1. 普通设置

set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False)
     #key: 键
     #value:值
     #max_age: 超时时间(单位:秒),建议使用。内部通过expires进行设置
     #expires: 超时时间(格式为datatime)
     #path: 设置针对该cookie生效路径,如:'/index',只能index下能访问。默认为“/”,所有路径均生效
     #domain: 设置针对该cookie生效域名,默认为当前域名
     #secure: 是否通过安全的HTTPS连接来传输(https时,设为ture)
     #httponly: 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

2. cookie签名(加盐)

#普通cookie是明文传输的,可以直接在客户端直接打开,所以需要加盐,解盐之后才能查看
set_signed_cookie(self, key, value, salt='', **kwargs)    
2.5.2 获取cookie

1. 普通获取

request.COOKIES.get('Cookie的key')

2. 签名获取

request.get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None)
2.5.3 删除cookie
response = HttpResponse('ok') or ...
response.delete_cookie("cookie_key", path="/", domain=name)
return response

2.6 示例--用户登录验证

from django.shortcuts import render, redirect, HttpResponse
import json, datetime
from datetime import timedelta


user_list = [
    {"username":"joe1","password":"123"},
    {"username":"joe2","password":"123"},
    {"username":"joe3","password":"123"},
]

def login(request):
    '''用户登录'''
    if request.method == "GET":
        return render(request, "login.html")
    else:
        user_name = request.POST.get("username")
        pwd = request.POST.get("password")
        for user in user_list:
            if user_name == user["username"] and pwd == user["password"]:
                obj = redirect('/page1/')
                # 通过max_age指定超时时间
                obj.set_cookie("ticket", '123456789', max_age=100)

                # 通过expires指定超时时间
                # now_time = datetime.datetime.utcnow()
                # delta = timedelta(seconds=10)
                # time_out = now_time + delta
                # obj.set_cookie("ticket",'123456789', expires=time_out)

                # cookie签名(加密)
                # obj.set_signed_cookie("ticket", '123456789', salt='abcdefg', max_age=10)

                return obj
        else:
            return render(request, "login.html")

def judge_status(func):
    '''用户登录状态判断'''
    def wrapper(request, *args, **kwargs):
        # 通过普通方式获取cookie
        cookie_value = request.COOKIES.get("ticket")

        # 通过加密方式获取cookie,不加try会报错,提示‘ticket’错误
        # try:
        #     cookie_value = request.get_signed_cookie('ticket',salt='abcdefg')
        # except Exception:
        #     cookie_value = False

        if cookie_value:
            ret = func(request, *args, **kwargs)
            return ret
        else:
            return render(request, "login.html")

    return wrapper

@judge_status
def page1(request):
    return render(request, 'page1.html')

@judge_status
def page2(request):
    return render(request, 'page2.html')
views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/login/">
        {% csrf_token %}
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password"></p>
        <p><input type="submit" value="提交"></p>
    </form>

</body>
</html>
login.html

未签名cookie样式:

image

签名cookie样式:

image

补充:

由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。

<script src='/static/js/jquery.cookie.js'></script>
$.cookie("list_pager_num", 30,{ path: '/' });

三 Session

3.1 Session简介

  • Session:保存在服务端的数据(本质是键值对)
  • 应用:依赖Cookie
  • 作用:保持会话,记住用户的登录状态(WEB网站,分布式架构)
  • 优点:敏感信息不会直接给客户端,防止客户端修改cookie信息

Session作用过程简图如下:

image

客户端Cookie:

image
服务端django_session数据库(默认):

image

注:如报错,先执行数据库迁移操作后再试试

python manage.py makemigrations
python manage.py migrate

3.2 Django中的Sesssion

Django中默认支持Session,并且默认将session数据存储在数据库中,即:django_session表中。其内部提供了5种类型的Session供我们使用:

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存加文件
  • 加密Cookie
3.2.1 数据库Session

1. 配置settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
  
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)。简单来说是否每次刷新更新失效时间

2. Session的使用

def use(request):
    # 获取、设置、删除Session中数据
    request.session['k1']  # 获取Session中key为‘k1’的数据,没有则报错
    request.session.get('k1', None)  # 获取Session中key为‘k1’的数据,没有返回None
    request.session['k1'] = 123  # 设置Session中key为‘k1’的值
    request.session.setdefault('k1', 123)  # 设置Session中key为‘k1’的值,存在则不设置
    del request.session['k1']  # 删除Session中key为‘k1’的键值对
    k1 = request.session.pop('k1')

    # 所有键、值、键值对
    request.session.keys()  # 获取Session中的key值
    request.session.values()  # 获取Session中的value值
    request.session.items()  # 获取Session中的key,value值
    request.session.iterkeys()  # 亲测报错,AttributeError: 'dict' object has no attribute 'iterkeys',待继续验证
    request.session.itervalues()  # 同上
    request.session.iteritems()  # 同上

    # 用户随机字符串
    request.session.session_key  # 获取用户session的随机字符串
    request.session.clear_expired()  # 将所有Session失效日期小于当前日期的数据删除
    request.session.exists("随机字符串")  # 检查用户session的随机字符串在数据库中是否存在
    request.session.delete("随机字符串")  # 删除当前用户的所有Session数据

    request.session.set_expiry(value)
    # 如果value是个整数,session会在些秒数后失效。
    # 如果value是个datatime或timedelta,session就会在这个时间后失效。
    # 如果value是0,用户关闭浏览器session就会失效。
    # 如果value是None,session会依赖全局session失效策略(优先级高于默认设置值)。 
3.2.2 其他

采用其他方式时,只需要在settings.py中对引擎进行设置,即可改变Session类型,使用与数据库Session一致。

1. 缓存Session

SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

注意:Django缓存相关内容请参考后续博客。

2. 文件Session

SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()

3. 缓存+数据库Session

#数据库用于持久化,缓存用于提高效率
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

4. 加密cookie Session

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

注:以上方式中数据库及缓存较为常用。

更多参考:

https://docs.djangoproject.com/en/1.9/topics/http/sessions/

https://docs.djangoproject.com/en/1.9/ref/settings/#settings-sessions

3.3 实例应用--用户登录验证

from django.shortcuts import render,HttpResponse,redirect

def login(request):
    '''用户登录'''
    if request.method == 'GET':
        return render(request,'login.html')
    else:
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        if username == 'joe1991' and pwd =='123':   #实际应用中应该从数据库获取相应用户信息进行比对,这里为了简便,直接指定
            # 1. 生成随机字符串
            # 2. 通过cookie发送给客户端
            # 3. 服务端保存{随机字符串1: {'username':'joe1991','pwd':'123'...}}
            request.session['username'] = username
            return redirect('/main/')
        else:
            return render(request, 'login.html')

def main(request):
    '''
    1. 获取客户端端cookie中的随机字符串
    2. 去session中查找有没有随机字符
    3. 去session对应的随机字符串的value中的key的value中查看是否有username
    '''
    username = request.session.get('username')
    if username:
        return HttpResponse('登录成功:%s' %username)
    else:
        return redirect('/login/')
views.py

当然,我们也可以使用装饰器:

def login_verify(func):
    def wrap(request, *args, **kwargs):
        # 如果未登陆,跳转到登陆页面
        if not request.session.get('username'):
            return redirect('/login/')
        else:
            return func(request, *args, **kwargs)
    return wrap

记录:同一浏览器的sessionid不变,切换用户登录也一样。但不同浏览器的sessionid不同。

3.4 Session注意事项

  • 每个用户的保存在服务端的sessionid是不一样的,不会造成用户间的信息混淆;
  • 如果A用户和B用户在同一个浏览器登录,sessionid不变,但是服务端session中的数据会被新用户覆盖;
  • 不同浏览器的sessionid不同;
  • 如果cookie泄露了,其他人拿着我们的cookie也是可以 访问服务端的,所以cookie一定要保存好。

总结:因为Session的优势,建议以后使用Session。

 

参考:http://www.cnblogs.com/wupeiqi/articles/5246483.html

posted @ 2018-09-30 18:37  Joe1991  阅读(143)  评论(0)    收藏  举报