Python社区  »  Python

Django cookie&session - 让浏览器记住你哦 | Python 主题月

程序媛小庄 • 2 月前 • 90 次点击  
阅读 38

Django cookie&session - 让浏览器记住你哦 | Python 主题月

本文正在参加「Python主题月」,详情查看 活动链接

微信公众号搜索【程序媛小庄】,Rest cannot be enjoyed by lazy people~

前言

小庄故事汇:小庄开了一个编程俱乐部,俱乐部最近遇到一个麻烦事,什么事呢?就是俱乐部是有会员和游客之分的,会员可以享受更多的服务,但是如何标记不同的用户种类可让小庄犯了难,于是小庄想了一个办法就是会员在俱乐部进行登记,俱乐部呢就告诉会员一个暗号,当顾客来到俱乐部时需要子包暗号,服务员一看暗号没错自然就认为你是会员了,可喜可贺可喜可贺,但是好景不长,小庄发现有游客偷听暗号冒充会员这可给爱财如命的小庄带来了巨大的损失,于是小庄不得不换个办法,于是小庄绞尽脑汁想到了另一个方法,就是会员卡,登记在册的会员发一张卡片,这张卡片上写着唯一的一串会员编号,当然俱乐部也找个小本本记一下每个会员编号对应的会员信息,当会员光顾俱乐部时就出示会员卡,如果能够通过会员卡上的编号找到小本本上的信息,就可以享受会员服务了,虽然卡片会丢但是损失总算比暗号这种扯着嗓子吼的方式安全的多。可喜可贺可喜可贺~

cookie和session简介

HTTP协议是无状态的,这意味这所有的客户端或者浏览器朝服务端发请求,服务端是不会记住客户端是谁,没办法保存用户的登录信息。

随之WEB的发展,出现了网上商城之类购物网站,这类网站的一个需求是记住当前用户是谁,并且需要记住用户的登录状态(避免每次请求页面都重新登录)。

为了解决这个需求,出现了很多总解决办法。其中最有效的一个办法是:当用户第一次登录成功后,将用户的登录信息(用户名和密码)返回给用户的浏览器,让浏览器将登录信息保存在本地浏览器(cookie);之后用户再次访问该网站时,浏览器会携带之前保存的该网站的登录信息,这样服务端获取登录信息之后自动登录验证。

但是这种方式存在很大的安全隐患:容易泄露用户的登录信息;后来又出现了优化的解决办法。

新的解决办法是:当用户初次登录成功后,服务端会产生一个随机字符串,该字符串保存在服务端(session),保存成键值对的形式。同时将该字符串交由客户端浏览器保存。之后再访问服务端的时候,浏览器都会带着这个随机字符串,服务端去数据库中匹配是否又这个随机字符串对应的用户信息;匹配成功则自动登录。

其实,如果截获到该随机字符串,那么就可以冒充当前用户,其实还是有安全隐患的。所以应了那句话:在web领域没有绝对的安全也没有绝对的不安全

这就是cookie和session的出现历程。

cookie

虽然cookie是服务端告诉客户端浏览器需要保存内容,但是客户端浏览器可以选择拒绝保存;如果将浏览器设置为禁止保存服务端的cookie,那么只要是需要记录用户状态的网站登陆功能都无法使用了。

服务端保存在客户端浏览器的信息都可以称之为cookie,在客户端浏览器中cookie的表现形式一般都是key:value键值对的形式。在浏览器的调试模式中打开application就可以看见cookie。

image-20210719152837769

django中操作cookie

django的视图函数的返回值一般有三种,分别是HttpResponse对象、render()redirect(),本质上都是返回的HttpResponse对象,因此我们可以直接返回也可以现将对象用变量保存下来再返回,如果想要在django中操作cookie的话就必须利用中间变量。

# 不使用cookie
return HttpResponse()
return render()
return redirect()

# 使用cookie
obj = HttpResponse()
操作cookie
return obj
复制代码

如何设置cookie和获取cookie以及删除cookie呢?

# 当用户访问服务端时,为客户端浏览器设置cookie,客户端浏览器保存该cookie
obj.set_cookie(key, value)

# 当用户访问需要登录才能进入的页面时,服务端需要获取客户端浏览器携带的cookie,进行校验
obj.COOKIES.get(key)

 # 删除用户浏览器上之前设置的usercookie值
obj.delete_cookie(key)		       
复制代码

有一些网站每过一段时间就会让你重新登录,就是因为设置了cookie的过期时间,在django中也可以为cookie设置过期时间:

obj.set_cookie(key, value, max_age=3)
obj.set_cookie(key, value, expires=3)

# 两个参数都是设置超时时间的,并且都是以秒为单位;需要注意的是,针对IE浏览器需要使用expires
复制代码

cookie版登录和注销

需求大概就是使用cookie完成登录和注销的功能,并且通过装饰器实现不登录就无法使用除注册外的功能,未登录时访问功能页面直接跳转到登录页面,登录成功之后再访问想要访问的页面。

首先是登陆装饰器,装饰器可以放在项目根目录下的utils目录(自己创建)中。

# utils/login_auth

from functools import wraps
from django.shortcuts import redirect


def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        target_url = request.get_full_path()                # 获取用户想要访问的url
        if request.COOKIES.get('is_login'):
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect(f'/login/?next={target_url}')   # 设置登录后跳转的页面url
    return inner
复制代码

下面是视图函数:

# views.py
from utils.login_auth import login_auth

# 登录页面
def login(request):
    info = {'msg':''}
    # 获取用户上一次想要访问的页面
    tag_url = request.GET.get('next')
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'python' and password == '123':
            if tag_url:
                obj = redirect(tag_url)
            else:
                # 不能直接返回redirect,因为直接返回是无法保存cookie的,必须使用obj返回,才能记录状态,浏览器客户端才会保存
                obj = redirect('/home/')
                # max_age就是设置cookie的有效期,单位是秒,当达到了有效期后,浏览器客户端会自动删除本地的cookie信息
            obj.set_cookie('is_login', 'this_user_has_been_logined', max_age=10000)
            return obj
        else:
            info['msg'] = '帐号密码错误'

    return render(request,'login.html',locals())

# 退出登录
@login_auth
def logout(request):
    response_obj = redirect('login')
    response_obj.delete_cookie('is_login')
    return


    
 response_obj


# 主页
@auth
def home(request):

    return HttpResponse('我是home页面')
复制代码

session

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

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

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

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

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

django中操作session

django中session的操作通过request对象下的session完成,它类似一个字典结构,操作方式和字典的操作非常类似。session中的数据是保存在服务端的数据库django_session表中,给客户端返回的是一个随机字符串,django默认session的过期时间是14天,可以认为修改它。django_session表中的数据条数是取决于浏览器的,同一个计算机上(IP地址)同一个浏览器只会有一条数据生效。主要是为了节省服务端数据库资源。

# 获取、设置、删除Session中数据
request.session['k1']					# 拿到'k1'对应的value
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			         # 随机字符串ryl3jza580roxfntx8wittr0vykvmi5h

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据
request.session.delete()		                  # 只删服务端的 客户端的不删
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内部发生的事情

当使用request.session['is_login'] = 'login'时会发生下述事情:

1.django内部会自动帮你生成一个随机字符串
2.django内部自动将随机字符串和对应的数据存储到django_session表中
     2.1先在内存中产生操作数据的缓存
     2.2在响应结果django中间件的时候才真正的操作数据库
        2.1先在内存中产生操作数据的缓存
		2.2在响应结果django中间件的时候才真正的操作数据库
		
		django_session表内存储格式
		session_key			session_data			expire_date
		随机字符串1			 对应数据密文			     过期时间	
     	随机字符串2			 对应数据密文			     过期时间	
		......
3.将产生的随机字符串返回给客户端浏览器保存
复制代码

用画图来来表示过程如下:

image-20210719173357712

使用request.get['is_login']时发生的事情:

1.自动从浏览器请求中获取sessionid对应的随机字符串
2.拿着该随机字符串去django_session表中查找对应的数据
3.如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中
  如果比对不上 则request.session.get()返回的是None
复制代码

CBV添加装饰器

实现登陆之后才能访问其他页面的需求在使用FBV的情况下,只需要为视图函数添加装饰器即可,那么CBV模式的视图类又如何实现此需求呢?这里介绍三种添加装饰器的方式,在这之前需要明确一点,CBV中django不建议直接给类的方法添加装饰器,不论装饰器是否生效。

CBV遇到需要加装饰器时,需要先导入from django.utils.decorators import method_decorator,在然后在指定方法上加@method_decorator(method_name),如果加在类上,还需要提供装饰的名字:@method_decorator(decorator_name, method_name),下面是添加装饰器的方式:

给方法加装饰器

from django.views import View
from django.utils.decorators import method_decorator
 
 
class MyLogin(View):
    
    @method_decorator(login_auth)  		# 方式1:给get方法添加装饰器
    def get(self,request):
        return HttpResponse("get请求")
 
    def post(self,request):
        return HttpResponse('post请求')
    
# 优点:简单直接
# 缺点:多个方法时,需要多次添加装饰器
复制代码

给类加装饰器

from django.views import View
from django.utils.decorators import method_decorator


@method_decorator(login_auth, name='get')  		# 同时提供装饰器和被装饰的方法
@method_decorator(login_auth, name='post')		# 支持装饰器多个叠加
class MyLogin(View):
   
    def get(self,request):
        return HttpResponse("get请求")

    def post(self,request):
        return HttpResponse('post请求')

# 优点:给不同的方法,添加不同的装饰器
复制代码

通过dispatch方法

from django.views import View
from django.utils.decorators import method_decorator


class MyLogin(View):
    
    @method_decorator(login_auth)  			# 方式3:它会直接作用于当前类里面的所有的方法
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request,*args,**kwargs)
   
    def get(self,request):
        return HttpResponse("get请求")

    def post(self,request):
        return HttpResponse('post请求')
    
# 优点:一次性给所有方法添加装饰器(某些特殊装饰器只能加在dispatch上,例如csrf认证)
# 缺点:灵活性差
复制代码

结语

文章首发于微信公众号程序媛小庄,同步于掘金

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)

image.png

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/117247
 
90 次点击  
分享到微博