python(十八):cookie和session

Posted 韩非囚秦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python(十八):cookie和session相关的知识,希望对你有一定的参考价值。

一、Cookie

  1、cookie机制

  会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

  在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于同一个会话的,不能放入用户B或用户C的购物车内,这不属于同一个会话。

  而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。

  Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

  关于cookie,需要记住几点:

    - 1.cookie是保存在用户浏览器的已加密的键值对

    - 2.可以被主动清除(浏览器界面、前端、后台)

    - 3.可以被"伪造"

    - 4.处于隐私保护的目的,禁止跨域共享:即www.googole.com和www.baidu.com各自的cookie不可被共享,因为域名对应的谷歌公司和百度公司服务器是不同的。

  2、cookie设置  

  在django中,cookie是在声明一个HttpResponse之后,通过set_cookie方法来设置的。它通过在响应头里Set-Cookie设置键值对来实现在浏览器客户端保存Cookie。

# views.py
from django.http import HttpResponse
# 打开源码
# HttpResponse类,继承了HttpResponseBase,在HttpResponse类中没有关于cookiede方法
class HttpResponse(HttpResponseBase):
    """
    An HTTP response class with a string as content.
    This content that can be read, appended to, or replaced.
    """
    streaming = False

    def __init__(self, content=b\'\', *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Content is a bytestring. See the `content` property methods.
        self.content = content
......

# 查看HttpResponseBase类
class HttpResponseBase:
    ......
    def set_cookie(self, key, value=\'\', max_age=None, expires=None, path=\'/\',
                   domain=None, secure=False, httponly=False):
        """
        Set a cookie.
        ``expires`` can be:
        - a string in the correct format,
        - a naive ``datetime.datetime`` object in UTC,
        - an aware ``datetime.datetime`` object in any time zone.
        If it is a ``datetime.datetime`` object then calculate ``max_age``.
        """
        self.cookies[key] = value
        if expires is not None:
            if isinstance(expires, datetime.datetime):
                if timezone.is_aware(expires):
                    expires = timezone.make_naive(expires, timezone.utc)
                delta = expires - expires.utcnow()
                # Add one second so the date matches exactly (a fraction of
                # time gets lost between converting to a timedelta and
                # then the date string).
                delta = delta + datetime.timedelta(seconds=1)
                # Just set max_age - the max_age logic will set expires.
                expires = None
                max_age = max(0, delta.days * 86400 + delta.seconds)
            else:
                self.cookies[key][\'expires\'] = expires
        else:
            self.cookies[key][\'expires\'] = \'\'
        if max_age is not None:
            self.cookies[key][\'max-age\'] = max_age
            # IE requires expires, so set it if hasn\'t been already.
            if not expires:
                self.cookies[key][\'expires\'] = cookie_date(time.time() +
                                                           max_age)
        if path is not None:
            self.cookies[key][\'path\'] = path
        if domain is not None:
            self.cookies[key][\'domain\'] = domain
        if secure:
            self.cookies[key][\'secure\'] = True
        if httponly:
            self.cookies[key][\'httponly\'] = True
    def setdefault(self, key, value):
        """Set a header unless it has already been set."""
        if key not in self:
            self[key] = value
  # 签名的cookie def set_signed_cookie(self, key, value, salt
=\'\', **kwargs): # salt加盐之后并加密;与它相应的用request.COOKIES.get_signed_cookie(...)来解密 value = signing.get_cookie_signer(salt=key + salt).sign(value) return self.set_cookie(key, value, **kwargs) def delete_cookie(self, key, path=\'/\', domain=None): # 删除cookie self.set_cookie(key, max_age=0, path=path, domain=domain, expires=\'Thu, 01-Jan-1970 00:00:00 GMT\') ...

  3、cookie参数

属  性  名 描    述
String name 该Cookie的名称。Cookie一旦创建,名称便不可更改
Object value 该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
int maxAge 该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1
boolean secure 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。当使用https式,必须要secure设置为Y=True。
String path 该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”
String domain 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”
boolean httponly 限制在浏览器控制台获取键值对,但无法对抓包工具进行限制。

  4、用例

from django.shortcuts import render, redirect
import datetime

def login(request):
    msg = ""
    # print(request.environ["Set-Cookie"])
    if request.method == "POST":
        user= request.POST.get("user", False)
        pwd = request.POST.get("pwd", False)
        if user == "root" and pwd == "root":
            red = redirect("index")    # 同JsonResponse, FileResponse, render, HttpResponse一样,redirect是HttpResponseBase的子类,red是一个httpresponse对象
            # red.set_cookie("username", user)
            # print(red.items())
            # print(red.serialize_headers())
            # print("cookie", red.cookies)
            # print(red.content)
            # print(red.status_code)
            # print(red.has_header("Cookie"))
            # red.set_cookie("key", "value", expires=datetime.timedelta(seconds=20), )
            red.set_cookie("key", "value", max_age=20, path="/app04/", domain="127.0.0.1", httponly=False)
            return red
        else:
            msg = "用户名或密码错误"
    return render(request, \'app04/login.html\', {"msg": msg})

  链接[https://blog.csdn.net/gaoyong_stone/article/details/79524321]

 二、Session

  1、session机制

  为了保持会话,客户端浏览器可以在用户登录后,将cookie从本地读入客户端内存;因为cookie放在请求头中,所以在服务端也可以通过request.COOKIE来获取所有的cookie值。服务端可以通过响应头中的Set-Cookie字段来告诉浏览器添加、修改或删除cookie。执行cookie的主体是客户端浏览器

  session则是在request到来时,通过SessionMiddleWare中间件,在进行视图函数执行之前,做了一些操作。它在Cookie中生成了一段随机字符串作为session id,并且将key-value随机化处理,存储到了服务器(django默认存在django_session表里)。

  来扒一下django的源码,彻底理清楚session的整个流程:

# 1.查找django.contrib.sessions.middleware.SessionMiddleware中间件,因为session是由这个中间件定义的,所以一定要看清它在一次请求中干了什么勾当
# from django.contrib.sessions.middleware import SessionMiddleware
# 2.点开SessionMiddleware,源码如下:

import time
from importlib import import_module

from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import cookie_date

class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
    # 7.self.SessionStore是一个session存储引擎的实例化对象
    # 它是根据settings.SESSIOn_ENGINE的值(默认是
django.contrib.sessions.backends.db)来导入相应的db模块【跳转到下面第二个文档】 engine
= import_module(settings.SESSION_ENGINE) self.SessionStore = engine.SessionStore   # process_request在调用视图函数之前被调用 def process_request(self, request):
     # 1.从request.COOKIES那里获取了一个默认您设置变量settings.SESSION_COOKIE_NAME作为session_key【跳转到下面第一个文档】

     session_key
= request.COOKIES.get(settings.SESSION_COOKIE_NAME) # 5.紧挨着下面的3.4.5步,得知sessionid是django自带的session_key的cookie中的名字名字
     # 6.生成一个reqeust属性,名为session,它的值是一个SessionStore对象,这个对象包含了accessed和modified
     
request.session
= self.SessionStore(session_key)
     # 10、根据下面的步骤9,可以知道request.session就是一个对象,这个对象可以以字典的形式添加键值对,并支持向django_session或者其它数据库(缓存)中写入/修改/删除操作。

  # process_response在返回响应前调用 def process_response(self, request, response): """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie or delete the session cookie if the session has been emptied. """ try:
       # 11.accessed不用管,看modified;在步骤9中得知,一但request.session传入了键值对,这货就是True accessed
= request.session.accessed modified = request.session.modified empty = request.session.is_empty() except AttributeError: pass else: # First check if we need to delete this cookie. # The session should be deleted only if the session is entirely empty if settings.SESSION_COOKIE_NAME in request.COOKIES and empty: response.delete_cookie( settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, ) else: if accessed: patch_vary_headers(response, (\'Cookie\',))
          # 12.如果session被设置,那么走这一步
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty: if request.session.get_expire_at_browser_close(): max_age = None expires = None else: max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. # Skip session save for 500 responses, refs #3881. if response.status_code != 500: try:
                
# 调用SessionStore.save()方法,往数据库写入session request.session.save() except UpdateError: raise SuspiciousOperation( "The request\'s session was deleted before the " "request completed. The user may have logged " "out in a concurrent request, for example." )
               # 13.在response响应前,通过response.set_cookie方法将sessionid(前面赋值了settings.SESSION_COOKIE_NAME)以及参数写到响应头中 response.set_cookie( settings.SESSION_COOKIE_NAME, request.session.session_key, max_age
=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None, )
     # 14.将response做上述处理后,将response交给下一个中间件
return response
  """
  2.在这里,根据from django.conf import settings打开settings,进到django.conf.__init__.py中
  3.再根据from django.conf import global_settings打开global_sesstings.py,可以看到有关session的设置如下:
    """
    """
    Default Django settings. Override these with settings in the module pointed to
    by the DJANGO_SETTINGS_MODULE environment variable.
    """
    ...
    ############
    # SESSIONS #
    ############

    # Cache to store session data if using the cache session backend.
    SESSION_CACHE_ALIAS = \'default\'
    # Cookie name. This can be whatever you want.
    SESSION_COOKIE_NAME = \'sessionid\'
    # Age of cookie, in seconds (default: 2 weeks).
    SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
    # A string like "example.com", or None for standard domain cookie.
    SESSION_COOKIE_DOMAIN = None
    # Whether the session cookie should be secure (https:// only).
    SESSION_COOKIE_SECURE = False
    # The path of the session cookie.
    SESSION_COOKIE_PATH = \'/\'
    # Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others)
    SESSION_COOKIE_HTTPONLY = True
    # Whether to save the session data on every request.
    SESSION_SAVE_EVERY_REQUEST = False
    # Whether a user\'s session cookie expires when the Web browser is closed.
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False
    # The module to store session data
    SESSION_ENGINE = \'django.contrib.sessions.backends.db\'
    # Directory to store session files if using the file session module. If None,
    # the backend will use a sensible default.
    SESSION_FILE_PATH = None
    # class to serialize session data
    SESSION_SERIALIZER = \'django.contrib.sessions.serializers.JSONSerializer\'
    """
    ...
  # 4.可以看到这是django默认的环境配置文件,并且文件开头,提示可以通过在项目文件夹下的settings.py重写变量来重新配置这些环境变量。
  """
# 8.通过 from django.contrib.sessions.backends import db 导入db.py,源码如下:
# 它做了两件事情:第一件,继承了SessionBase类,这个类生成了session字典,并提供了该字典的增删改差的基本操作;第二件,自己在这个字典对象上又添加了一些额外的静态方法和实例方法
# 这些静态方法和实例方法主要用于操作缓存或者数据库中的django_session表
# 接着点开SessionBase,它的源码文件如下面内容所示【跳转到下面】

... from django.contrib.sessions.backends.base import ( CreateError, SessionBase, UpdateError, ) ...
class SessionStore(SessionBase): """ Implement database session store. """ def __init__(self, session_key=None): super().__init__(session_key) @classmethod def get_model_class(cls): # Avoids a circular import and allows importing SessionStore when # django.contrib.sessions is not in INSTALLED_APPS. from django.contrib.sessions.models import Session return Session @cached_property def model(self): return self.get_model_class() def load(self)def exists(self, session_key): return self.model.objects.filter(session_key=session_key).exists() def create(self) def create_model_instance(self, data)def save(self, must_create=False) def delete(self, session_key=None) @classmethod def clear_expired(cls)
# 9.这个SessionBase就是所有配置session数据库的基类,它规定了session字典层面上的操作,包括增删改查以及对age、expire、encode等的设置
# 【跳转回第一个文件】

...
# session_key should not be case sensitive because some backends can store it # on case insensitive file systems. ... class SessionBase: """ Base class for all Session classes. """ TEST_COOKIE_NAME = \'testcookie\' TEST_COOKIE_VALUE = \'worked\' __not_given = object() def __init__(self, session_key=None): self._session_key = session_key # 注意:初始化self._session_key = None,但是当设置了session键值对之后,self._session_key就成了字典 self.accessed = False self.modified = False self.serializer = import_string(settings.SESSION_SERIALIZER) def __contains__(self, key)def __getitem__(self, key)def __setitem__(self, key, value):
    self._session_key = value
    self.modified = True # 注意,一旦session添加了一个键值对,self.modified的值就变成了True
def __delitem__(self, key)def get(self, key, default=None)def pop(self, key, default=__not_given)def setdefault(self, key, value):def set_test_cookie(self):def test_cookie_worked(self)def delete_test_cookie(self)def _hash(self, value)def encode(self, session_dict)def decode(self, session_data)def update(self, dict_)def has_key(self, key)def keys(self)def values(self)def items(self)def clear(self)def is_empty(self)def _get_new_session_key(self)def _get_or_create_session_key(self)def _validate_session_key(self, key) def _get_session_key(self) def _set_session_key(self, value)def _get_session(self, no_load=False)def get_expiry_age(self, **kwargs)def get_expiry_date(self, **kwargs)def set_expiry(self, value)def get_expire_at_browser_close(self)def flush(self)def cycle_key(self) def exists(self, session_key)def create(self)def save(self, must_create=False)def delete(self, session_key=None)def load(self) @classmethod def clear_expired(cls)

  上面的整个流程如下图所示:

  总结一下session和cookie:

    - session和cookie一样,都是通过response.set_cookie来设置的;

    - session将名为"sessionid"(默认)的key交给浏览器保存,将键值对(session_key和session_date)存储在服务器;cookie将键值对直接保存到客户端浏览器文件夹下;

    - session借助SessionMiddle中间件实现了对request.session对象的生成和对response.set_cookie的设置,分别在process_request和process_response里;cookie直接在视图函数中写即可;

    - 要记住django.contrib.sessions.backends是用来搞session的文件夹,request.session数据库读写方法在.db.SessionStore类里,requesion.session字典操作方法在.base.SessionBase里

  2、session配置

  在django.conf.global_settings文件中包含了对所有django默认环境变量的配置,这里把源码拉出来看一下(500行):

"""
Default Django settings. Override these with settings in the module pointed to
by the DJANGO_SETTINGS_MODULE environment variable.
"""


# This is defined here as a do-nothing function because we can\'t import
# django.utils.translation -- that module depends on the settings.
def gettext_noop(s):
    return s


####################
# CORE             #
####################

DEBUG = False

# Whether the framework should propagate raw exceptions rather than catching
# them. This is useful under some testing situations and should never be used
# on a live site.
DEBUG_PROPAGATE_EXCEPTIONS = False

# Whether to use the "ETag" header. This saves bandwidth but slows down performance.
# Deprecated (RemovedInDjango21Warning) in favor of ConditionalGetMiddleware
# which sets the ETag regardless of this setting.
USE_ETAGS = False

# People who get code error notifications.
# In the format [(\'Full Name\', \'email@example.com\'), (\'Full Name\', \'anotheremail@example.com\')]
ADMINS = []

# List of IP addresses, as strings, that:
#   * See debug comments, when DEBUG is true
#   * Receive x-headers
INTERNAL_IPS = []

# Hosts/domain names that are valid for this site.
# "*" matches anything, ".example.com" matches example.com and all subdomains
ALLOWED_HOSTS = []

# Local time zone for this installation. All choices can be found here:
# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
# systems may support all possibilities). When USE_TZ is True, this is
# interpreted as the default user time zone.
TIME_ZONE = \'America/Chicago\'

# If you set this to True, Django will use timezone-aware datetimes.
USE_TZ = False

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = \'en-us\'

# Languages we provide translations for, out of the box.
LANGUAGES = [
    (\'af\', gettext_noop(\'Afrikaans\')),
    (\'ar\', gettext_noop(\'Arabic\')),
    (\'ast\', gettext_noop(\'Asturian\')),
    (\'az\', gettext_noop(\'Azerbaijani\')),
    (\'bg\', gettext_noop(\'Bulgarian\')),
    (\'be\', gettext_noop(\'Belarusian\')),
    (\'bn\', gettext_noop(\'Bengali\')),
    (\'br\', gettext_noop(\'Breton\')),
    (\'bs\', gettext_noop(\'Bosnian\')),
    (\'ca\', gettext_noop(\'Catalan\')),
    (\'cs\', gettext_noop(\'Czech\')),
    (\'cy\', gettext_noop(\'Welsh\')),
    (\'da\', gettext_noop(\'Danish\')),
    (\'de\', gettext_noop(\'German\')),
    (\'dsb\', gettext_noop(\'Lower Sorbian\')),
    (\'el\', gettext_noop(\'Greek\')),
    (\'en\', gettext_noop(\'English\')),
    (\'en-au\', gettext_noop(\'Australian English\')),
    (\'en-gb\', gettext_noop(\'British English\')),
    (\'eo\', gettext_noop(\'Esperanto\')),
    (\'es\', gettext_noop(Python爬虫知识点——Session与Cookie

Python之路66-Django中的Cookie和Session

python爬虫cookie & session

[oldboy-django][4python面试]cookie和session比较

doraemon的python cookie和session(国庆大更新)

python web框架补充cookie和session(Django)