zoukankan      html  css  js  c++  java
  • 1204 中间件以及cookie,session

    一 .cookie与session原理

    cookie,session,token介绍

    为什么会有这些技术

    • 目的:为了保存客户端的用户状态
    • 原因:HTTP协议的无状态的

    1.cookie

    什么是Cookie

    Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
    

    Cookie的原理

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

    保存在客户端浏览器上的键值对

    cookie虽然是保存在客户端浏览器上的键值对
    
    但是他是有服务端设置的
    
    浏览器有权进制cookie的写入
    

    操作

    利用obj对象才可以操作cookie

    obj = HTTPResponse()
    return obj
    
    obj = render()
    return obj
    
    obj = redirect()
    return obj
    
    告诉浏览器设置cookie
    obj.set_cookie('k1','k2')
    

    1.2 获取cookie request.COOKIES.get('k1')

    request.COOKIES.get('k1')
    获取浏览器携带过来的cookie
    

    1.3 设置cookie的超时时间 max_age = num

    obj.set_cookie('k1','k2',max_age=3)
    obj.set_cookie('k1','k2',expires=3)
    
    两个参数都是设置超时时间,并且都是以秒为单位
    区别:
    	如果给IE浏览器设置cookie的超时时间只能使用expires
    
    • request.path_info 只获取url
    • request.get_full_path() 获取url+get请求的参数

    1.4 删除cookie delete_cookie('k1')

    注销或退出登录

    obj.delete_cookie('cookie名')
    

    代码

    简单版

    def login(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            if username == 'a' and password == '123':
                # 登录成功,定位到主页home
                obj = redirect('/home/')
                # 设置cookie值,告诉浏览器保存键值对
                obj.set_cookie('whoami','a')
                return obj
        return render(request,'login.html')
    
    def home(request):
        # 校验用户是否登录
        if request.COOKIES.get('whoami'):
            return HttpResponse('只有登录的用户才能访问')
        # 否则返回登录界面
        return redirect('/login/')
    
    

    登录认证自动跳转

    没有登录之前访问的界面,自动跳转到登录界面之后,登录成功直接跳转到登录之前界面
    
    
    def login(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            if username == 'a' and password == '123':
                # 登录成功,获取登录之前的所在界面
                old_path = request.GET.get('next')
                # 也可能直接登录,存在则定向到old界面
                if old_path:
                    obj = redirect(old_path)
                else:
                    # 没有想要的网站直接跳转到主页
                    obj = redirect('/home/')
                # 设置cookie值,告诉浏览器保存键值对
                obj.set_cookie('whoami','a')
                return obj
        return render(request,'login.html')
    
    # 装饰器函数
    from functools import wraps
    def login_auth(func):
        @wraps(func)
        def inner(request,*args,**kwargs):
            # 判断当前用户是否登录
            if request.COOKIES.get('whoami'):
                res = func(request,*args,**kwargs)
                return res
            else:
                # 获取用户的所输入地址
                target_path = request.path_info
                # 利用重定向携带参数
                # http://127.0.0.1:8000/login/?next=/index/
                return redirect('/login/?next=%s'%target_path)
        return inner
    
    @login_auth
    def home(request):
        return HttpResponse('登录之后才能查看')
    
    
    @login_auth
    def index(request):
        return HttpResponse('index界面, 登录之后才能查看')
    
    @login_auth
    def reg(request):
        return HttpResponse('reg界面, 登录之后才能查看')
    
    

    2.session

    保存在服务端上面的键值对

    session的工作机制是依赖于cookie的

    session流程解析

    img

    Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
    
    问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
    
    我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
    
    总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
    
    另外,上述所说的Cookie和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失效策略。
    
    

    2.1 设置session session[k]=v

    request.session['k'] = 'v'
    可以设置多个session,服务端数据库只有一条信息
    获取时,明文获取即可
    
    
    第一次设置的时候会报错,因为没有执行数据库的迁移命令,生成django需要用到的一些默认表(django_session)
    
    django默认的session失效时间是14天	两周
    
    

    img

    img

    django session在创建数据的时候,是针对浏览器,多条数据多个浏览器

    执行的发生顺序

    1. django内存自动帮你调用算法生成了一个随机的字符串
    2. 在django_session数据库表中添加数据(数据也是加密处理)

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

    ​ sfdfs dsdfsadfsdf 推延2周

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

      sessionid:随机字符串形式保存

    2.2 获取session session.get

    request.session.get('k')
    
    

    发生的顺序

    1. django内部会自动去请求头里面获取cookie
    2. 拿着sessionid所对应的随机字符串去django_session表中一一对比
    3. 如果比对成功,会将随机字符串对应的数据取出来,自动放入request.session中供程序员调用,如果没有就是一个空字典

    2.3 删除session session.delete()

    只会根据浏览器的不同删除对应的数据

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

    2.4 设置超时时间 set_expiry(value)

    request.session.set_expiry(value)
    
    

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

    3.token

    加密字符串

    		加密算法  xxx
    		
    		username   》(xxx)》   随机字符串
    		
    		username + 随机字符串
    
    

    1、很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议, 就是请求加响应, 尤其是我不用记住是谁刚刚发了HTTP请求, 每个请求对我来说都是全新的。这段时间很嗨皮

    2、但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样, 每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了

    3、这样大家很嗨皮了,可是服务器就不嗨皮了,每个人只需要保存自己的session id,而服务器要保存所有人的session id ! 如果访问服务器多了, 就得由成千上万,甚至几十万个。

    这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统, 那session id会保存在机器A上, 假设小F的下一次请求被转发到机器B怎么办? 机器B可没有小F的 session id啊。

    有时候会采用一点小伎俩: session sticky , 就是让小F的请求一直粘连在机器A上, 但是这也不管用, 要是机器A挂掉了, 还得转到机器B去。

    那只好做session 的复制了, 把session id 在两个机器之间搬来搬去, 快累死了。

          img

    后来有个叫Memcached的支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了, 所有人都得重新登录一遍, 估计得被人骂死。

           img

    也尝试把这个单点的机器也搞出集群,增加可靠性, 但不管如何, 这小小的session 对我来说是一个沉重的负担

    4 于是有人就一直在思考, 我为什么要保存这可恶的session呢, 只让每个客户端去保存该多好?

    可是如果不保存这些session id , 怎么验证客户端发给我的session id 的确是我生成的呢? 如果不去验证,我们都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。

    嗯,对了,关键点就是验证 !

    比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来不就可以了。

    不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。

    那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。

    img

    这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。

    img

    Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。

    当然, 如果一个人的token 被别人偷走了, 那我也没办法, 我也会认为小偷就是合法用户, 这其实和一个人的session id 被别人偷走是一样的。

    这样一来, 我就不保存session id 了, 我只是生成token , 然后验证token , 我用我的CPU计算时间获取了我的session 存储空间 !

    解除了session id这个负担, 可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。 这种无状态的感觉实在是太好了!

    django中间件

    https://www.cnblogs.com/Dominic-Ji/p/9229509.html

    1.django流程图

    img

    2. 中间件定义

    imgimg

    中间件是在requestresponse处理过程中的一个插件。

    比如在request到达视图函数之前,我们可以使用中间件来做一些相关的事情,比如可以判断当前这个用户有没有登录,如果登录了,就绑定一个user对象到request上。

    也可以在response到达浏览器之前,做一些相关的处理,比如想要统一在response上设置一些cookie信息等。

    3. 自定义中间件

    django中默认有七个中间件

    只要想要做一些网站的全局性功能,应该考虑使用django的中间件

    • 全局性的用户登录校验

    • 全局的用户访问频率校验

    • 全局的用户权限校验

      django中间件是所有框架中做的最完善的
      
      对象,字符串   ===   反射
      
      全局         ===   中间件
      
      

    并且支持用户自定义中间件,然后暴露给用户五个可以自定义的方法

    需要掌握

    • process_request
    • process_response

    需要了解

    • process_view
    • process_template_response
    • process_exception

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

    如果形参中含有response,那么必须要返回

    中间件所处的位置没有规定。只要是放到项目当中即可。
    一般分为两种情况,如果中间件是属于某个app的,那么可以在这个app下面创建一个python文件用来存放这个中间件,也可以专门创建一个Python包,用来存放本项目的所有中间件。
    创建中间件有两种方式,一种是使用函数,一种是使用类,接下来对这两种方式做个介绍:
    
    

    1. process_request

    # 自定义类中间件
    class MyMdd1(MiddlewareMixin):
        def process_request(self,request):
            print('我是第一个中间件里的process_request方法')
    
    
    • 请求来的时候会按照settings配置文件中从上至下的顺序,依次执行每一个中间件内部定义的process_ request方法,如果中间件内部没有改方法,直接跳过执行下一个中间件

    • 该方法一旦返回了HttpResponse对象,那么请求会立刻停止往后走,原路返回

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

    2.process_response

    自定义中间件
    class MyMdd1(MiddlewareMixin):
    
        def process_response(self,request,response):
            print('我是第一个中间件里的process_response方法')
            
            # 必须返回response
            return response
    
    
    • 响应走的时候会按照settings配置文件中从上往下的顺程序,一次执行每一个中间件内部定义的process_response方法
    • 该方法必须有两个形参,并且必须返回response形参,不返回直接报错
    • 该方法返回什么(HttpResponse对象),前端就能获得什么

    img

    3. process_view

        def process_view(self,request,view_name,*args,**kwargs):
            print('我是第一个中间件里面的process_view方法')
    
    
    • 路由匹配成功之后执行,视图函数触发之前
    • 如果该方法返回了HttpResponse对象 那么会从下往上一次经过每一个中间件里面的process_response方法

    img

    4.process_exception

        def process_exception(self,request,exception):
            username
            print('我是第一个中间件process_exception方法')
            print(exception)
    
    
    • 视图函数中出现错误时,会自动触发,顺序是从下往上

    img

    5.process_template_response

    视图函数

    def mdzz(request):
        print('== 我的视图函数mdzz ==')
        def render():
            return HttpResponse('自定义一个render函数')
        obj = HttpResponse('获取http对象')
        # 给对象赋值了render属性
        obj.render = render()
        return obj
    
    
    • 视图函数中,当你返回的对象中含有render属性指向的是一个render方法的时候才会触发,从下往上的顺序

    • process_template_response(self, request, response)
      
      它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
      
      process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
      
      

    自定义中间件

    1.app下创建文件夹,里面是保存中间件的.py文件
    2.在py文件中新建中间件类,并且在settings中注册中间件的地址
    
    -------------------------------------------------------
    from django.utils.deprecation import MiddlewareMixin
    
    
    # 自定义类中间件
    class MyMdd1(MiddlewareMixin):
        def process_request(self,request):
            print('我是第一个中间件里的process_request方法')
    
    
        def process_response(self,request,response):
            print('我是第一个中间件里的process_response方法')
            # 必须返回response
            return response
    
        def process_view(self,request,view_name,*args,**kwargs):
            print(view_name)
            print('我是第一个中间件里面的process_view方法')
    
        def process_exception(self,request,exception):
            print('我是第一个中间件process_exception方法')
            print(exception)
    
    
    class MyMdd2(MiddlewareMixin):
        def process_request(self, request):
            print('我是第二个中间件里的process_request方法')
    
        def process_response(self,request,response):
            print('我是第二个中间件里的process_response方法')
            # 必须返回response
            return response
    
        def process_view(self,request,view_name,*args,**kwargs):
            print(view_name)
            print('我是第二个中间件里面的process_view方法')
    
    
        def process_exception(self,request,exception):
            print('我是第二个中间件process_exception方法')
            print(exception)
    
    
  • 相关阅读:
    客户端session与服务端session
    对session和cookie的一些理解
    Servlet生命周期与工作原理
    Linux命令行编辑快捷键
    含有GROUP BY子句的查询中如何显示COUNT()为0的成果(分享)
    设计模式学习——准备(UML类图)
    find()方法
    js中的动态效果
    动态添加,移除,查找css属性的方法
    VUE中的require ( )
  • 原文地址:https://www.cnblogs.com/fwzzz/p/11986431.html
Copyright © 2011-2022 走看看