django之cookie和session

刚才小编了这样一个故事,分享记录在此,希望有些许收获。

简介

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

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

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

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

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

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

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

总结

  • cookie:服务端保存在客户端浏览器上的信息,它的表现形式一般都是k:v键值对(可以有多个)。cookie就是保存在客户端浏览器上的信息
  • session:数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)。session就是保存在服务端上的信息
  • session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)。
# 补充:
token
session虽然数据是保存在服务端的 但是禁不住数据量大
    服务端不再保存数据
    登陆成功之后 将一段用户信息进行加密处理(加密算法之后你公司开发知道)
    将加密之后的结果拼接在信息后面 整体返回给浏览器保存 
    浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
    跟浏览器尾部的密文进行比对
    
jwt认证
    三段信息
    (后期再讲 结合django一起使用) 

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

cookie基本操作

视图函数的返回值一般有三种:HttpResponse对象、render()redirect()。其实本质上内部返回的都是HttpResponse对象。我们可以直接返回也可以先将对象用一个变量保存下来再返回。

# 方式1
return HttpResponse()
return render()
return redirect()

# 方式2
obj1 = HttpResponse()
return obj1

obj2 = render()
return obj2

obj3 = redirect()
return obj3

方式2看似是多此一举,但是这样的操作方式却提供了操作HttpResponse对象的可能。我们操作cookie就是通过这个对象来操作的。

设置cookie

obj.set_cookie(key, value)		# 设置cookie

获取cookie

request.COOKIES[key]
request.COOKIES.get(key)		# 从request中获取cookie

设置过期时间

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

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

删除cookie

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

cookie版登录和注销

# 需求:
	使用cookie完成登录和退出功能,且通过装饰器实现未登录时无法使用其他功能。
	未登录时,访问功能页面直接跳转到登录页面;登录成功后再跳转到想要访问的功能页面。

cookie登录校验技术点总结

#1 因为每个视图函数都至少有一个request形参,所以在写装饰器的时候,在inner中将request形参拎出来

#2 需要保存用户跳转到登录页面前想要访问的url,
	-2.1 在装饰器内通过request.get_full_path()拿到目标路径target_url;
        -2.2 验证失败在跳转到login时将target_url通过'?next=target_url'的方式放在login的url后面

#3 进入login的视图函数,获取当前url路径后携带的target_url,在登录验证成功后跳转到target_url
	-3.1 通过request.GET.get('next')拿target_url时可能为空,这时直接跳转到自己规定的主页'index'

#4 注意注意注意:login.html中form表单的action属性值有多中写法,如
	-4.1 action=''
        -4.2 action='{{request.get_full_path}}'
    
# 但是不能使用action={% url 'login'%}		
# 此时反向解析url时固定使用的url变成了:'/login/',这导致无法获取?next的值(无法实现登录后跳转到登录前想要访问的页面)

登录装饰器:utils/login_auth.py

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):
    if request.method == 'POST':
        if login_success:# 伪代码
            target_url = request.GET.get('next') or 'index'    # 登录前要访问的页面或者直接到index
            response_obj = redirect(target_url)
            response_obj.set_cookie('is_login', 'this_user_has_been_logined')
            return response_obj

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



@login_auth
def logout(request):
    response_obj = redirect('login')
    response_obj.delete_cookie('is_login')
    return response_obj


@login_auth
def book_list(request):
    book_queryset = models.Book.objects.all()
    page_obj, page_queryset = get_page_queryset(request, book_queryset)
    return render(request, 'book_list.html', locals())

前端模板文件:login.html

<form action="" method="post" novalidate>
    <p class="text-center">
        <input type="submit" class="btn btn-info" value="登录">
        <a href="{% url 'register' %}" class="small pull-right">没有账号,注册一个</a>
    </p>
</form>

django中cookie其他操作方法

# 获取cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
    default: 默认值
    salt: 加密盐
    max_age: 后台控制过期时间
        
        
# 设置cookie
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获取(不是绝对,底层抓包可以获取到也可以被覆盖)

session

session的由来

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

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

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

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

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

session的基本操作

django中session的操作通过request对象下的session完成,它类似一个字典结构,操作方式和字典的操作非常类似。

补充:

  • session数据是保存在服务端的(存?),给客户端返回的是一个随机字符串
    sessionid:随机字符串
  • 在默认情况下操作session的时候需要django默认的一张django_session表
  • django默认session的过期时间是14天,可以认为修改它。
  • session是保存在服务端的,但是session的保存位置可以有多种选择:数据库、文件等等
  • 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内部那些事

设置sessionrequest.session['login_auth_key'] = 'is_login'

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

获取sessionrequest.session.get('login_auth_key')

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

session流程解析

session版登录和注销

使用session和使用cookie的原理时一摸一样的。

登录装饰器:utils/login_auth.py

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.session.get('login_auth_key')
            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):
    if request.method == 'POST':
        if login_success:# 伪代码
            target_url = request.GET.get('next') or 'index'    # 登录前要访问的页面或者直接到index
            request.session['login_auth_key'] = 'is_login'
            return redirect(target_url)

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



@login_auth
def logout(request):
    request.session.flush()
    return redirect('login')

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,默认修改之后才保存(默认)

CBV添加装饰器

基于类的视图函数支持三种添加装饰器的方法,这里以登录校验为例介绍三种添加装饰器的方式。

首先明确一点,CBV中django不建议你直接给类的方法加装饰器(不论装饰器是否有效)。

# 函数装饰器
def login_auth(func):
def inner(request, *args, **kwargs):
  target_url = request.get_full_path()
  
  if request.session.get('login_auth_key'):
      res = func(request, *args, **kwargs)
      return res
  else:
      return redirect(f'/login/?next={target_url}')
return inner

cbv遇到要加装饰器时

  • 需要先导入 from django.utils.decorators import method_decorator

  • 在指定方法上方加@method_decorator(method_name)

  • 如果加在类上,还需要提供装饰的名字:@method_decorator(decorator_name, method_name)

  • 以下三种方式介绍其基本使用方式。

方式1:给方法加装饰器

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请求')
    
# 优点:简单直接
# 缺点:多个方法时,需要多次添加装饰器

方式2:给类加装饰器

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请求')

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

方式3:通过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认证)
# 缺点:灵活性差
posted @ 2020-06-06 11:27  the3times  阅读(211)  评论(2编辑  收藏  举报