cookie、session和中间件

  • 2019 年 12 月 16 日
  • 筆記

cookie和session

cookie与session原理

cookie是保存在浏览器上的键值对,session是保存在服务端的键值对,cookie和session存在的目的是保存用户的登录状态,那么为什么有cookie和session呢?这时因为HTTP协议的无状态、无连接的特点,也就是浏览器访问过服务器后如果断开连接,服务器不会记录浏览器的访问状态,这时候就需要利用cookie和session保存用户的登录状态。

cookie是保存在客户端浏览器上的键值对,不过它是由服务端在用户登录的时候设置的。如果在浏览器端如果禁止cookie我们将无法登录需要用户登录的网站这是服务端识别到浏览器禁用了cookie而做的优化。

Google浏览器查看cookie

右键检查

Django操作cookie

获取cookie

request.COOKIES['key']  request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

设置cookie

rep = HttpResponse(...)  rep = render(request, ...)    rep.set_cookie(key,value,...)  rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数:

  • key, 键
  • value='', 值
  • max_age=None, 超时时间
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  • domain=None, Cookie生效的域名
  • secure=False, https传输
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除cookie

def logout(request):      rep = redirect("/login/")      rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值      return rep

cookie登录认证

from functools import wraps    def check_login(func):      '''登录认证装饰器'''      @wraps(func)      def inner(request, *args, **kwargs):          next_url = request.get_full_path()#获取用户当前页面的url          if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":              # 已经登录的用户...              return func(request, *args, **kwargs)          else:              # 没有登录的用户,跳转刚到登录页面              return redirect("/login/?next={}".format(next_url))#将当前的url保存到登录页面url的后缀      return inner      def login(request):      if request.method == "POST":          username = request.POST.get("username")          passwd = request.POST.get("password")          if username == "xxx" and passwd == "xxx":              next_url = request.GET.get("next")#从登录页面url的后缀获取用户之前登录的页面的url              if next_url and next_url != "/logout/":                  response = redirect(next_url)#登录成功之后重定向到登录页面之前的页面              else:                  response = redirect("/home/")#如果url后缀没有信息,就重定向到home页面              response.set_signed_cookie("login", "yes", salt="SSS")              return response      return render(request, "login.html")

浏览器端的cookie

这里需要说明的是Django在后端没有专门用于存储cookie的表,但是同一用户在不同的浏览器登录产生的cookie仍是不一样的,只是cookie加密的时候需要使用用户信息,(如果只用字符串进行加密密钥会比较短),如果使用用户信息(set_signed_cookie)会得到第一条加密信息。

Django操作session

session的由来

Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本

另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

Django中session相关的方法

# 获取、设置、删除Session中数据  request.session['k1']  request.session.get('k1',None)  request.session['k1'] = 123  request.session.setdefault('k1',123) # 存在则不设置  del request.session['k1']      # 所有 键、值、键值对  request.session.keys()  request.session.values()  request.session.items()  request.session.iterkeys()  request.session.itervalues()  request.session.iteritems()    # 会话session的key  request.session.session_key    # 将所有Session失效日期小于当前日期的数据删除  request.session.clear_expired()    # 检查会话session的key在数据库中是否存在  request.session.exists("session_key")    # 删除当前会话的所有Session数据  request.session.delete()      # 删除当前的会话数据并删除会话的Cookie。  request.session.flush()      这用于确保前面的会话数据不可以再次被用户的浏览器访问      例如,django.contrib.auth.logout() 函数中就会调用它。    # 设置会话Session和Cookie的超时时间  request.session.set_expiry(value)      * 如果value是个整数,session会在些秒数后失效。      * 如果value是个datatime或timedelta,session就会在这个时间后失效。      * 如果value是0,用户关闭浏览器session就会失效。      * 如果value是None,session会依赖全局session失效策略。

设置session

利用上面的方法对session进行设置,设置完成后需要执行数据迁移命令,将设置保存到数据库的django_session中,这是Django默认的session值存储表。Django在设置session时是针对浏览器的,如果同一台电脑的同一浏览器,多用户登录时在数据库中只会产生一条记录,但是不影响各个用户对session值的取用。

request.session['k1'] = 'v1' 这句话Django内部帮你做的事情:

1.内部自动调用算法生成一个随机字符串(这个字符串是唯一的,如果同一用户在不同的电脑上登录得到的字符串是不一样的,但是都是基于‘k1’产生的。

2.在Django_session添加数据,(数据也是经过加密处理之后的)

保存到django_session表中的数据是

随机字符串                 加密之后的数据             失效时间

3.将产生的随机字符串返回给浏览器,让浏览器保存

sessionid:随机字符串

通过上面两张图可以看出,浏览器端存的字符串就是数据库中的session_key,而产生session的语句是request.session['k1'] = 'v1' ,取出使用request.session.get("v1"),而数据库中的name是随机产生的字符串,可以看出字符串是通过k1随机生成的,k1和字符串是有某种转换关系的。

获取session

request.session.get('k1')

Django会自动去请求头里获取cookie(sessionid),拿着sessionid所对应的随机字符串去django_sessoion表中一一比对,如果比对上了,会将随机字符串对应的数据获取出来 自动放入request.session中供程序员调用,如果没有就是一个空字典。

删除session

request.session.delete() 客户端和服务端全部删除session,会根据浏览器的不同删对应的数据

设置失效时间

request.session.set_expiry(value)

  • 如果value是个整数,session会在些秒数后失效。
  • 如果value是个datatime或timedelta,session就会在这个时间后失效。
  • 如果value是0,用户关闭浏览器session就会失效。
  • 如果value是None,session会依赖全局session失效策略。

cookie与session登录流程

session版登录验证

from functools import wraps      def check_login(func):      '''登录装饰器'''      @wraps(func)      def inner(request, *args, **kwargs):          next_url = request.get_full_path()          if request.session.get("user"):              return func(request, *args, **kwargs)          else:              return redirect("/login/?next={}".format(next_url))      return inner      def login(request):      if request.method == "POST":          user = request.POST.get("user")          pwd = request.POST.get("pwd")            if user == "ylpb" and pwd == "xxx":              # 设置session              request.session["user"] = user              # 获取跳到登陆页面之前的URL              next_url = request.GET.get("next")              # 如果有,就跳转回登陆之前的URL              if next_url:                  return redirect(next_url)              # 否则默认跳转到home页面              else:                  return redirect("/home/")      return render(request, "login.html")      @check_login  def logout(request):      # 删除所有当前请求相关的session      request.session.delete()      return redirect("/login/")      @check_login  def home(request):      current_user = request.session.get("user", None)      return render(request, "home.html", {"user": current_user})

Django中的session配置

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。

1. 数据库Session  SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)    2. 缓存Session  SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎  SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置    3. 文件Session  SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎  SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()    4. 缓存+数据库  SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎    5. 加密Cookie Session  SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎    其他公用设置项:  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,默认修改之后才保存(默认)

django中间件

介绍

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

由于中间件是全局的,当我们需要做一些全局性的功能时应该首先选择中间件,如:全局的用户登录校验、全局的用户访问频率的校验、全局的用户权限校验(用中间件是相当简单的),这里需要说一点django的中间件是所有框架里面做的最完善的,并且支持用户自定义中间件。

我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。

MIDDLEWARE = [      'django.middleware.security.SecurityMiddleware',      'django.contrib.sessions.middleware.SessionMiddleware',      'django.middleware.common.CommonMiddleware',      'django.middleware.csrf.CsrfViewMiddleware',      'django.contrib.auth.middleware.AuthenticationMiddleware',      'django.contrib.messages.middleware.MessageMiddleware',      'django.middleware.clickjacking.XFrameOptionsMiddleware',  ]

MIDDLEWARE配置项是一个列表(列表是有序的,记住这一点,后面你就知道为什么要强调有序二字),列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。这一个个字符串前面是文件夹和py文件,后面是一个个类如图:

我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。

那接下来就学习中间件中的方法以及这些方法什么时候被执行。

自定义中间件

自定义中间件的方法

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self,request,response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

自定义中间件示例

process_request和process_response

process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。

由于request对象是一样的,所以我们可以对request对象进行一系列的操作,包括request.变量名=变量值,这样的操作,我们可以在后续的视图函数中通过相同的方式即可获取到我们在中间件中设置的值。

它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

我们来看看多个中间件时,Django是如何执行其中的process_request方法的。

from django.utils.deprecation import MiddlewareMixin  from django.shortcuts import HttpResponse    class TestMiddleware(MiddlewareMixin):      def process_request(self, request):          print('test中间件')          return HttpResponse('test')      def process_response(self,request, response):          print('process_response')          return response    class TestMiddleware2(MiddlewareMixin):      def process_request(self, request):          print('test中间件2')          return HttpResponse('test2')        def process_response(self,request, response):          print('process_response2')          return response

在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:

MIDDLEWARE = [      'django.middleware.security.SecurityMiddleware',      'django.contrib.sessions.middleware.SessionMiddleware',      'django.middleware.common.CommonMiddleware',      #'django.middleware.csrf.CsrfViewMiddleware',      'django.contrib.auth.middleware.AuthenticationMiddleware',      'django.contrib.messages.middleware.MessageMiddleware',      'django.middleware.clickjacking.XFrameOptionsMiddleware',      'app01.TestMiddleware.TestMiddleware',      'app01.TestMiddleware.TestMiddleware2'  ]

此时,我们访问一个视图,会发现终端中打印如下内容:

test中间件  process_response

当我们注销第一个自定义中间件的return HttpResponse('test')后得到

test中间件  test中间件2  process_response2  process_response

我们可以得到如下结论

process_request

1.请求来的时候会按照settings配置文件中从上往下的顺序,依次执行每一个中间件内部定义的process_request方法,如果中间件内部没有该方法直接跳过执行下一个中间件。 2.该方法一旦返回了HttpResponse对象,那么请求会立刻停止往后走 原路立即返回。

3.当process_request方法直接返回HttpResponse对象之后会直接从当前中间件里面的process_respone往回走,没有执行的中间件都不会再执行。

process_response

1.响应走的时候会按照settings配置文件中从下往上的顺序 依次执行每一个中间件内部定义的process_response方法 2.该方法必须有两个形参,并且必须返回response形参,不返回直接报错 3.该方法返回什么(HttpResponsed对象) 前端就能获得什么

中间件的其他方法

process_view(self,request,view_name,*args,**kwargs) 1.路由匹配成功之后执行视图函数之前触发。 2.如果该方法返回了HttpResponse对象 那么会从下往上依次经过每一个中间件里面的process_response方法。 process_template_response 1.当返回的对象中含有render属性指向的是一个render方法的时候才会触发 ,从下往上的顺序执行。

def mdzz(request):      print('我是视图函数')      def render():          return HttpResponse('xxx')      obj = HttpResponse('yyy')      obj.render = render      return obj

process_exception

当视图函数中出现错误会自动触发顺序是从下往上。

上面这五个方法会在特定的阶段自动触发。