cookie和session

cookie和session

cookie和session简介

HTTP协议中规定了浏览器不会保存客户端的状态,即无状态的连接特性。这是因为早期的web不需要什么用户注册,看看新闻的功能,所有用户都给一样的界面,不需要什么状态保存。

但随着互联网的发展,我们很多web框架的应用需要保存用户的状态,如最经典的登录状态。

cookie和session是服务端创造出来用于校验的虎符。

  • cookie就是保存在客户端与用户状态相关的信息
  • session就是保存在服务端与用户状态相关的信息

session的工作需要依赖于cookie,而浏览器有权拒绝服务端发送过来的cookie数据

django操作cookie

发送cookie

我们在返回HttpResponse响应对象前,在响应对象COOKIES属性中添加键值对,就可以将cookie保存在客户端的浏览器中了。

# 用户登录
def login_func(request):
    if request.method == 'POST':  # 提交表单打算登录
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'leethon' and password == '123':  # 校验密码成功的话
            obj = redirect('/home/')  # 重定向到比如说首页,但是响应对象不直接return
            obj.set_cookie('name', username)  # return前设置cookie属性,一个name字段保存的用户名
            return obj
    return render(request, 'loginPage.html')  # get请求拿登录界面

检验cookie

浏览器所有cookie在浏览器发送请求时都会携带,并存放在request的COOKIES属性中,我们可以取出校验。

# 访问首页
def home_func(request):
    if request.COOKIES.get('name'):  # 如果COOKIES里面有name,说明有用户登录
    	return HttpResponse('home页面 只有登录的用户才可以查看')  # 用户登录状态下直接访问页面
    return redirect('/login/')  # 用户未登录则跳转到登录界面

登录装饰器综合cookie运用

一个web应用中可能有很多页面都需要登录才能访问,那么这个登录功能我们可以写成装饰器,如果需要就加装,不需要登录则不装。

而且,在访问网页跳转到登录页面时,我们应该让用户登录后可以跳转回原本的页面,也就是obj = redirect('/home/')这一句中的路由不应该写死到返回首页。

要实现上述两个需求就需要编写以下登录功能和装饰器

def login_auth(func):
    def inner(request, *args, **kwargs):  # 视图函数一定含request参数,所以单独先接收便于操作
        target_path = request.path_info  # 拿到请求的路由
        if request.COOKIES.get('name'):  # 校验用户态
            res = func(request, *args, **kwargs)  # 用户登录态则直接返回原本访问的网页
            return res
        return redirect(f'/login/?next={target_path}')  # 将被装饰的视图函数的路由发送给login函数
    return inner

def login(request):
    if request.method == 'POST':  # 提交表单打算登录
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'leethon' and password == '123':  # 校验密码成功的话
            if not request.GET.get('next'):
                obj = redirect('/home/')  # 如果不是通过装饰器重定向来的,就重定向到首页
            else:
	            obj = redirect(f'/{request.GET.get("next")}/')  # 有next就重定向到原本的网页
            obj.set_cookie('name', username)  # 保存登录态
            return obj
    return render(request, 'loginPage.html')  # get请求拿登录界面

@login_auth
def home(request):
    return HttpResponse('home页')

@login_auth
def shop(request):
    return HttpResponse('shop页')

@login_auth
def personal(request):
    return HttpResponse('personal页')

上述代码中,装饰器中的重定向让路由携带了GET请求数据,数据中包含被装饰的视图函数的路由信息;在登录函数中又拿回GET请求数据中的原路由,重定向又返回了用户原本想访问的网页,形成了一套完美的combo。

request有path/path_info/get_full_path()三个有关路由信息的属性

前两种是获得路由(不带?后数据),而get_full_path()方法获得完整路由(携带?后数据)

request.path/path_info:/home/ request.get_full_path():/home/?name=lee

cookie操作补充

设置cookie时可额外携带一些参数:

HttpResponse对象.set_signed_cookie(key,
                        value,
                        salt='加密盐', 
                        max_age=None,
                        expires=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

删除cookie就是删除客户端所保存的用户状态,web中注销用户登录的操作就是用这条语句实现的。

HttpResponse对象.delete_cookie(key)

django操作session

在上述cookie的校验中,很明显感觉到只校验COOKIES里面有没有数据显然不严谨,我们应该让用户的cookie与服务端的某个数据再次校验一下,如用户登录时发送了cookie的name存储了用户名,再次访问页面时就让服务端拿到请求时取出cookie的name,比对name是否在我的数据库中。

当然这样也还是不安全,相当于用户知道了用户名就可以通过cookie来访问网页,所以现如今的session,就帮助我们更安全的存储数据。

  • 首先session是存在数据库的表中,django默认执行迁移命令后就会产生一张django_session表存储
  • session默认存储的结构是三个字段,分别是session键、session值和过期时间
  • 校验用户态就是将随机生成的session键交给用户的cookie,将用户信息如用户名存到session值中。当用户再带着cookie过来时,就将cookie的值和表中所有session键比对,如果有对应的session键,就对应的拿到保存着对应用户态的session值。
  • session键和session值都是密文格式。
  • 过期时间expire_data的默认间隔是两周。

设置session

request.session['key'] = value  # value为我想存到session_data字段的值,会自动加密 

底层原理:

  1. 生成一个随机字符串作为session_key存入django_session的session_key字段

  2. 对value数据做加密处理,并在django_session表中存储

    以上两条的随机字符串键和加密value值是一一对应的关系(同属一条记录)

  3. 将随机字符串也发送一份给客户端保存(cookie)

获取session

request.session.get('key', None)  # 拿到cookie中的session字符串(键key要与设置的相对应)
# 第二个参数是没有取到对应session记录而赋值的默认值(如没有对应用户则是游客)

底层原理:

  1. 获取cookie中的session随机字符串
  2. 去django_session表中根据随机字符串获取加密的数据
  3. 自动解密数据并处理到request.sesion.get()的返回值中

这样我们就可以在存储用户的加密数据在服务端,用户端浏览器cookie只保存一个用于校验的随机字符串,这样就很安全。

设置expire_time间隔

过期时间expire_data的默认间隔是两周,我们也可以自定义这个间隔:

request.session.set_expiry(60)  # 数字单位秒
  • 如果value是个整数,session会在指定秒数后失效。
  • 如果value是个datatime或timedelta,session就会在这个时间后失效。
  • 如果value是0,用户关闭浏览器session就会失效。
  • 如果value是None,session会依赖全局session失效策略。

session方法补充

session方法很多类似于字典的方法,当然,它底层做的事要更加的多。

session方法汇总
# 删除Session中数据
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版本的用户登录功能
装饰器、自动记忆跳转、过期时间

class Login(forms.Form):
    username = forms.CharField(max_length=8, min_length=2,
                               # widget=forms.widgets.TextInput(),
                               error_messages={
                                   'max_length': "啊 长了",
                                   'min_length': "啊 短了",
                               }
                               )  # 字符字段,限定最大最小长度
    password = forms.CharField(max_length=16, min_length=6,
                               widget=forms.widgets.PasswordInput(),
                               )

    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')
        user_obj = models.User.objects.filter(username=username).first()
        if not user_obj:
            self.add_error('username', '用户名不存在')
        elif not user_obj.password == password:
            self.add_error('password', '密码不正确')
        return self.cleaned_data


def login_auth(func):
    def inner(request, *args, **kwargs):  # 视图函数一定含request参数,所以单独先接收便于操作
        target_path = request.path_info  # 拿到请求的路由
        username = request.session.get('key')  # 拿到登录态
        # username = request.COOKIES.get('name')
        if username:  # 校验用户态
            res = func(request, *args, **kwargs)  # 用户登录态则直接返回原本访问的网页
            return res
        return redirect(f'/login/?next={target_path}')  # 将被装饰的视图函数的路由发送给login函数

    return inner


def login(request):
    login_obj = Login()
    if request.method == 'POST':  # 提交表单打算登录
        login_obj = Login(request.POST)
        if login_obj.is_valid():  # 数据有效且密码校验也正确
            if not request.GET.get('next'):
                obj = redirect('/home/')  # 如果不是通过装饰器重定向来的,就重定向到首页
            else:
                obj = redirect(request.GET.get("next"))  # 有next就重定向到原本的网页
            request.session['key'] = login_obj.cleaned_data.get('username')  # 保存登录态
            # obj.set_cookie('name', username)  # 保存登录态
            return obj
    return render(request, 'loginPage.html', locals())  # get请求拿登录界面


@login_auth
def home(request):
    return HttpResponse('home页')

@login_auth
def home1(request):
    return HttpResponse('home1页')
posted @ 2022-12-22 22:03  leethon  阅读(39)  评论(0编辑  收藏  举报