Fork me on GitHub

cookie与session组件

一 会话跟踪技术

什么是会话跟踪

我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向10086发出多个请求,那么这多个请求都在一个会话中。
在Web中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。

在一个会话的多个请求中共享数据,这就是会话跟踪技术。例如在一个会话中的请求如下:  请求银行主页;

  • 请求登录(请求参数是用户名和密码);
  • 请求转账(请求参数与转账相关的数据);
  • 请求信誉卡还款(请求参数与还款相关的数据)。

在这上会话中当前用户信息必须在这个会话中共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款!这就说明我们必须在一个会话过程中有共享数据的能力。

会话路径技术是用Cookie或session完成的

我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!在Web开发中,使用session来完成会话跟踪,session底层依赖Cookie技术。

二 cookie组件

cookie的由来

大家都知道HTTP协议是无状态的。

无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。

就好比用户登录商城之后购物车添加1,然后发送添加请求,服务器记不得你之前的登录状态所以需要重新提交登录请求,最后才能获得你的用户信息,所以在web应用必须在用户浏览其他商品时保持已经登录的状态,那么就需要这一系列添加商品的操作都要在一个会话中保持,多次请求共享一个数据,即在这个会话中就是该用户发送多次请求,不必每次请求都需要校验身份。因此cookie就在这样一个场景下诞生。

什么是cookie

其实Cookie是key-value结构,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!

cookie常见的登录案例

  • 首先用户在客户端浏览器向服务器首次发起登录请求
  • 登录成功后,服务端会把登录的用户信息存在cookie中,并将cookie返回给客户端浏览器
  • 客户端浏览器接收到 cookie 请求后,会把 cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
  • 以后再次访问该 web 应用时,客户端浏览器就会把本地的 cookie 带上,这样服务端就能根据 cookie 获得用户信息了,有了用户信息就能访问该用户在数据库中的数据

比如查询数据与改数据都需要登录认证,如果没有cookie情况下,登录之后发送查数据的请求,服务端返回数据给客户端,这时我需要根据查好的数据进行更改,然后发送相关请求,因为服务器是无状态,它认为你这次请求与上次请求无关,所以这次请求没有登录认证需要进行登录,然后再返回时可能是你自己设定的重定向的登录页面了。所以这两次请求就是两次会话,是数据隔离的。

如果加上cookie,登录成功之后,就会返回cookie到你的客户端中,里面有你的身份标识,然后每一次发送请求都会带着这个标识,所以可以在一个会话中发送多个需要登录认证的请求,且不需要重复的登录,直接通过cookie中的标识在服务器上得到权限,那么在查到数据后,还可以继续进行改操作,因为这几个事件都有登录状态的认证在同一个会话中。

cookie的原理

cookie的工作原理是:由服务器产生cookie,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

cookie规范

  • Cookie大小上限为4KB;
  • 一个服务器最多在客户端浏览器上保存20个Cookie;
  • 一个浏览器最多保存300Cookie;

上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。

cookie的覆盖

如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。

在浏览器中查看cookie

浏览器中按F12,点network---cookies就能看到

Django中操作cookie

# 添加cookie
def set_cookie(request):
    # 浏览器向我这个地址发一个请求,就在浏览器写入 name = arther
    obj = HttpResponse('ok')
    obj.set_cookie('name', 'egon')  # 写入到浏览器了,在http响应头里:cookie:name=egon
    obj.set_cookie('age', '18')  # 写入到浏览器了,在http响应头里:cookie:age=18
    return obj


'''

Set-Cookie: name=egon; Path=/
Set-Cookie: age=18; Path=/

'''


# 得到cookie

def get_cookie(request):
    # 浏览器向我这个地址发一个请求,就在浏览器写入 name = arther
    print(request.COOKIES)
    print(request.COOKIES.get('name'))
    return HttpResponse('我拿了你传过来的cookies')


# 删除cookie
def delete_cookie(request):
    obj = HttpResponse('我删除了你 name 这个cookie')
    obj.delete_cookie('name')
    return obj

参数:

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间
rep = HttpResponse(...)
rep = render(request, ...)

rep.set_cookie(key,value)
rep.set_signed_cookie(key,value,salt='加密盐')
  • key, 键
  • value='', 值
  • max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
  • domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
  • secure=False, 浏览器将通过HTTPS来回传cookie
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
                   domain=None, secure=False, httponly=False)

# key
# value
# max_age:传个数字,以秒计,过期时间,有默认值 (6天后过期:60*60*24*5)
---了解
# expires:传时间对象,date=datetime.timedelta()
# path:默认 / 表示当前域下的所有路径  http://127.0.0.1:8000/lqz/dfd/
# domain:在那个域下有效,比如登录后在当前服务器的另一个域中没有登录状态
# secure:是否Https传输cookie
# httponly:cookie只支持http传输

Cookie版登陆校验

# 登陆的视图函数,成功则返回cookie中带有'is_login'=True(可以加盐)
# 之后的请求中cookie带有'is_login'=True,即可访问该用户
def login(request):
    if request.method == 'POST':

        name = request.POST.get('name')
        pwd = request.POST.get('pwd')
        print(name)
        all_info = models.Userinfo.objects.all()

        for user_obj in all_info:
            if name == user_obj.name and pwd == user_obj.password:
                import datetime
                now = datetime.datetime.now().strftime('%Y-%m-%d %X')
                print(now)
                obj = JsonResponse({'code': 1, 'info': '登录成功'})
                obj.set_cookie('is_login', True)
                obj.set_cookie('username', name)
                obj.set_cookie('login_time', now)

                return obj
        else:
            flag = {'code': 0, 'info': '登录失败'}
            return JsonResponse(flag)
          
          
          
# 写成装饰器
def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('is_login'):
            return func(request, *args, **kwargs)
        else:
            url = reverse('login')
            return redirect(url)

    return inner


三 Session组件

session的由来

cookie虽然在一定程度上解决了'保持状态'的需求,但是由于cookie本身最大支持4096字节,以及它本身保存在客户端,会被拦截或切取,如上述的登录装饰器,登录成功后在cookie中设置'is_login'=True来标识一个用户的身份与权限。如果有人在本地客户端窃取登录成功后cookie,然后再次发送该请求即可盗用该用户,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器(相对安全,在服务器不易被窃取),有较高的安全性。这就是Session。

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

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

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

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

img

session组件的使用

1.存在于服务端的键值对,最终会存在session表中,有id,data,date三个字段,保存的数据会以加密的形式如用户信息拼接到字段data下的记录,然后随即生成一个字符串做为session_id来标识该用户身份,该id会放在cookie中返回到客户端的浏览器中,然后每次访问带上这个cookie就是已经登录的用户的访问

2.同一个浏览器不允许登录多个账户,不同浏览器可以登录同一个账户

3.session的使用(必须迁移数据)
		-增:request.session['name']=lqz
    -查:request.session['name']
    -改:request.session['name']=egon
    -删:del request.session['name']
    -设置过期时间:request.session.set_expiry(10)
    
4.session的其他使用
		-request.session.setdefault('k1',111)  # 有该key值则不设置,没有则设置
  	-request.session.get('name',None)      
    -del request.session['k1']
    
    # 所有 键、值、键值对
    -request.session.keys()
		-request.session.values()
    -request.session.items()
    
    -request.session.session_key
    # 获取那个放入cookie中的随机字符串,也是session表中用来标识用户身份的字段
    -request.session.exists("session_key")
    # 判断这个随机字符串有没有数字
    # 注意在登录过程中还没有形成session_key,所以此时为None,在登录校验完后响应浏览器返回数据		时经过中间件形成的session_key
    
		-request.session.delete()  # 在session表中删除当前这个登录者的数据,
    -request.session.flush()   # 所以当请求来时cookie中的session_id匹配不上session表的数																 据校验失败,需要刷新将失效cookie设置为过期

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_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)***记住

---了解
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)

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_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

四 cgb装饰器

from django.views import View
from django.utils.decorators import method_decorator
# 使用登录认证装饰器:多了一个self参数,所以调用模块method_decorator
# 用法一
# @method_decorator(login_auth,name='get')
# @method_decorator(login_auth,name='post')
class UserInfo(View):
    # 用法二
    @method_decorator(login_auth)
    def get(self, request, *args, **kwargs):
        return HttpResponse('userinfo get')
    
    
# 总结:两种用法
	-加在类上:@method_decorator(login_auth,name='get')
  -加载方法上:@method_decorator(login_auth)

五 cookie与session的不同

  • 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
  • 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。

六 练习

基于session与cbv写一个登录认证装饰器,四个视图(登录,退出,订单,用户信息)
-必须登录才能访问订单和用户信息
-没登录访问订单,会重定向到登录,登录成功后跳转到订单页面

# views.py
from django.shortcuts import render, HttpResponse, redirect, reverse
from app10.appform import MyForm
from django.utils.decorators import method_decorator
from django.forms import Form
from django.views import View

flag = None 

# 全局变量,作为判断依据渲染到home.html文件中,如果为None是初始页面有登录没退出,如果登录成功改# # 为True重定向到home中,则是没有登录有退出,然后退出成功则是改回None,有退出没登录


# Create your views here.

def auth(func):
    def wrapper(request, *args, **kwargs):
        flag = request.COOKIES.get('sessionid')
        if flag:  # 如果有sessionid
            if flag == request.session.session_key:  
              # 该sessionid与数据库中的key相等,则判定成功
              # 如果是没有校验flag是否存在,那么两者有可能都是none相等
              # 为了防止改动sessionid也能登录的场景
                return func(request, *args, **kwargs)  
              

            else:
                return redirect(reverse('login'))

        else:
            return redirect(reverse('login'))

    return wrapper


class Login(View):
    def get(self, request):
        form = MyForm()

        return render(request, 'login.html', {'form': form})

    def post(self, request):
        form = MyForm(request.POST)
        global flag
        if form.is_valid():
            flag = True
            request.session['is_login'] = True

            return redirect(reverse('home'))
        else:

            error = form.errors.get('__all__')  # 获取全局钩子的错误信息
            if error is None:
                return render(request, 'login.html', {'form': form})

            return render(request, 'login.html', {'form': form, 'error': error})


class Home(View):
    def get(self, request):
        print(flag)
        return render(request, 'home.html', {'flag': flag})


class Logout(View):

    def get(self, request):
        global flag
        flag = None
        request.session.delete()
        request.session.flush()

        return redirect(reverse('home'))


@method_decorator(auth, name='get')
class Info(View):
    def get(self, request):
        return HttpResponse('用户信息')


@method_decorator(auth, name='get')
class Order(View):
    def get(self, request):
        return HttpResponse('订单')


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    {% load static %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
    <script src="{% static 'js/bootstrap.js' %}"></script>
</head>
<body>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">

                <li style="display: block" id="login"><a href="{% url 'login' %}">登录</a></li>
                <li style="display: none" id="logout"><a href="{% url 'logout' %}">退出</a></li>
                <li><a href="{% url 'info' %}">用户信息</a></li>
                <li><a href="{% url 'order' %}">订单</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">Dropdown <span class="caret"></span></a>

{% if flag %}
    <script>
        $('#login').css('display', 'none')//登录成功后flag=True,那就是登录有,退出没有
        $('#logout').css('display', 'block')
    </script>
{% else %}

    <script>
        $('#login').css('display', 'block') //最开始flag=None,相应最开始的页面也是登录有,退出没有
        $('#logout').css('display', 'none')

    </script>
{% endif %}

<script>
    $('#logout').click(function () {
        alert('退出成功!')

    })
</script>


</body>
</html>
posted @ 2020-10-22 17:54  artherwan  阅读(145)  评论(0编辑  收藏  举报