Django高级之-cookie与session

1 会话跟踪技术

1.1 什么是会话跟踪

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

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

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

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

1.2 会话路径技术使用Cookie或session完成

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


 

2 cookie介绍

2.1 web发展史

第一阶段:网站都没有保存用户功能的需求,所有用户访问返回的结果都是一样的,如:新闻网站、文章、博客。

第二阶段:出现了一些需要保存用户信息的网站,如:淘宝、支付宝、京东。

为什么要保存用户登录状态?以登陆功能为例:如果不保存用户登陆状态,也就意味着用户每次访问网站都需要重复的输入用户名和密码。

早期解决思路,当用户第一次登陆成功之后,将用户的用户名密码返回给用户浏览器让用户浏览器保存在本地,之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证,早起这种方式具有非常大的安全隐患。

2.2 Cookie的由来

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

一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。

状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。

2.3 什么是Cookie

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

2.4 Cookie的原理

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

2.5 Cookie规范

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

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

2.6 Cookie的覆盖

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

2.7 在浏览器中查看cookie

 

3 Session介绍

3.1 Session的由来

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

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

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

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

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

3.2 什么是Session

当用户登陆成功之后,服务端产生一个随机字符串,用kv键值对的形式在服务端保存数据(k就随机字符串,v是用户私密信息),服务端把随机字符串交由客户端浏览器保存,之后浏览器访问服务端的时候,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息。

3.3 总结

cookie就是保存在客户端浏览器上的信息;

session就是保存在服务端上的信息;

session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)。


 

4 Django中操作Cookie

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

如果你想要操作cookie,你就不得不利用HttpResponse对象,即视图函数不直接返回,而是先赋值给一个HttpResponsej对象。

4.1 获取Cookie

#把COOKIES当做字典,http请求发过来的是一堆字符串-->wsgi解析封装成字典-->django框架中间件把字典拆开-->封装到Request对象,把cookie携带的键值对拼到request.COOKIES字典中
#浏览器传过来的cookie信息是在请求头里,通过request.Meta可以获取,然后放到字典中

request.COOKIES.get(key)  
# request.COOKIES['key'] 

#获取加盐的cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

4.2 设置Cookie

obj1 = HttpResponse(...)
obj2 = render(request, ...)
obj3 = redirect(...)

#本质:http响应有个cookie,一旦设置了,就把键值对信息包装到http响应的cookie的value值里面了,浏览器会保存下来
obj1.set_cookie(key,value,...) #自己设置kv键值对让浏览器保存

obj2.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...) #设置cookie时可以加盐

obj3.set_cookie('user', 'jason', max_age=3, expires=3, path=url, domain=None)

# 参数
max_age 设置超时时间,以秒为单位,如果设置为None,则延续到浏览器关闭,下次打开就没有cookie
expires 设置超时时间,以秒为单位,针对IE浏览器
path    设置cookie的生效路径,只在某个url有效,不会带到站点的其他应用;path='/', 表示根路径,根路径的cookie可以被任何url的页面访问
domain  默认是所有域名都有效,比如map.baidu.com、fanyi.baidu.com百度的多个域都有效,只想要某个域名生效,单独配置domain=map.baidu.com
secure=False, 浏览器将通过HTTPS来回传cookie

4.3 删除Cookie

def logout(request):
    obj = redirect("/login/")
    obj.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return obj

4.4 Cookie版登录校验

用户如果在没有登陆的情况下想访问一个需要登陆的页面,那么先跳转到登陆页面,当用户输入正确的用户名和密码之后,应该跳转到用户之前想要访问的页面去。

推导过程

#第一步:先实现一个基础的登录功能

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        if username == 'jason' and password == '123':  # 此处应该查询数据库
            # 跳转到一个需要用户登录后才能看到的页面
            return redirect('/home/')
    return render(request, 'login.html')

# 问题:在浏览器输入这个页面url也能直接访问,说明根本没有限制必须登录才能访问。因此,在用户登录后不能直接跳转,要先用cookie保存用户状态


#第二步:利用cookie让浏览器帮你存储登录状态,在没有cookie记录下,访问不到home页面
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        if username == 'jason' and password == '123':
            # 不能直接返回跳转,先赋值给一个变量obj
            obj = redirect('/home/')
            # 让浏览器记录cookie数据,手动放入键值对,可以通过代码动态生成
            obj.set_cookie('username', '666')
            """
            浏览器不单单帮你存储这个键值对
            而且每次你访问的时候,它会带着这个键值对来做认证
            """
            return obj
    return render(request, 'login.html')

def home(request):
    # 获取cookie信息 判断你的浏览器有没有这个键值对信息
    if request.COOKIES.get('username') == '666':
        return HttpResponse('我是主页,只有登录后才能看到我')
    # 没有登录信息,则跳转到登录页面
    else:
        return redirect('/login/')


#第三步:如果多个页面都需要登录后才能访问,都需要验证登录状态,则需要写一个登录认证装饰器
#先写一个装饰器模板
def login_auth(func):
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return inner

#对于home函数,装饰器的作用就是用来判断cookie,需要用到request参数,request本来是放在*agrs中的,对装饰器做个变形,把request单独拎出来
def login_auth(func):
    def inner(request, *args, **kwargs):
        # 做cookie判断,如果能拿到值,就正常执行被装饰对象的功能
        if request.COOKIES.get('username'):
            res = func(request, *args, **kwargs)
            return res
        # 拿不到值,就让你去登录页面
        else:
            return redirect('/login/')
    return inner

#对于home函数,就单独写它的功能,不需要去验证cookie了
@login_auth
def home(request):
    return HttpResponse('我是主页,只有登录后才能看到我')


#第四步:有多个功能都需要登录后才能访问,在没有登录的情况下,用户访问这些页面会跳转到login页面,当用户输入正确的用户名与密码后,应该跳转到用户之前想要访问的页面,而不是写死,都朝home页面跳转
两种方式进入login页面
    1、直接访问login, 登录后应该跳转到你默认的一个页面,如home页面
    2、没有登录的情况下访问到其他页面被转到了login,登录后应该跳转到你上次访问那个页面
    
在输入了正确的用户名和密码后,控制跳转的是login函数中的obj = redirect('/home/'),写死了,要为它传值
第一种情况默认跳转home,第二种情况需要传入用户上次访问的那个url

核心知识点:request.path_info         获取用户输入的url
            request.get_full_path()   获取用户输入的url及?后携带的数据
    
知识点应用:return redirect('/login/?next=%s' % target_url)
    -登录认证装饰器,在用户访问需要登录的页面会跳转到login
    -跳转时在login后用?把用户上一次访问页面url传过去
    -在访问login页面时,http://127.0.0.1:8000/login/?next=/index/ 就能拿到这个target_url

cookie登录校验完整版

# 登录认证装饰器
def login_auth(func):
    def inner(request,*args,**kwargs):
        # print(request.path_info)
        # print(request.get_full_path())  
        target_url = request.get_full_path()  # 能够获取到用户上一次想要访问的url
        if request.COOKIES.get('username'):
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/?next=%s'%target_url)  # 拿不到值,就让你去登录页面,从哪个页面转过来的,就把这个页面url放在login后面
    return inner

# 登录功能视图函数
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':

            # 获取用户上一次想要访问的url
            target_url = request.GET.get('next')  # 这个结果可能是None,如果有值,说明是访问其他页面跳转到login的,登录后跳转到该url
            if target_url:
                obj = redirect(target_url)
            else:
                obj = redirect('/home/')  # 如果为None,说明是直接访问的login,登录后跳转到默认页面
            obj.set_cookie('username', 'jason666')  # 让浏览器记录cookie数据
            """
            浏览器不单单会帮你存
            而且后面每次访问你的时候还会带着它过来
            """
            return obj  # 都先不直接返回跳转,先设置cookie,再返回obj
    return render(request,'login.html')


# 其他需要登录后才能访问的页面
@login_auth
def home(request):
    return HttpResponse('我是主页,只有登录后才能看到我')

@login_auth
def index(request):
    return HttpResponse('我是index页面,只有登录后才能看到我')

@login_auth
def user_list(request):
    return HttpResponse('我是用户展示页面,只有登录后才能看到我')

# 注销功能
@login_auth
def logout(request):
    # 注销后返回登录页面
    obj = redirect('/login/')
    obj.delete_cookie('username')
    return obj

 

5 Django中Session相关方法

session数据是保存在服务端的,给客户端返回的是一个随机字符串,如 sessionid:随机字符串

在默认情况下操作session的时候需要django默认的一张django_session表,因为数据要保存在服务端,默认用session表储存,也可以用文件或缓存储存

执行数据库迁移命令时,django会自动创建很多表,django_session表就是其中的一张,在设置session前,如果没有操作过数据库,需要先执行数据库迁移命令。

# 生成随机字符串
# 写浏览器cookie -> session_id: 随机字符串
# 写到服务端session -> {"随机字符串": {'user':'alex'}}

5.1 session基本使用

设置session 

request.session['key'] = value  
request.session.setdefault('key',123)   # 存在则不设置

#django内部会自动帮你生成一个随机字符串
#django内部自动将随机字符串(key)和对应的数据(value)存储到django_session表中
#将产生的随机字符串返回给客户端浏览器保存,sessionid:随机字符串
    -先在内存中产生操作数据的缓存
    -在响应结果走到django中间件的时候才真正的操作数据库

获取session

request.session.get('key')

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

注销session

request.session.delete()    # 删除当前会话的所有Session数据,只删服务端的,客户端的不删
request.session.flush()     # 删除当前的会话数据并删除会话的Cookie,浏览器和服务端都清空(推荐使用)

del request.session['key']

5.2 session其他

# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key(默认为sessionid)
request.session.session_key

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

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta(日期对象),session就会在这个指定时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。
    
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

5.4 扩展知识点

session是保存在服务端的,但是session的保存位置可以有多种选择:

1.MySQL     #默认为数据库session
2.文件      #文件Session
3.redis     #缓存Session,默认内存缓存
4.memcache  #缓存session

在设置session时,可以同时设置多个,用函数def set_session(request),获取session时,通过request.session对象能获取到你设置的所有数据。

如果多个视图函数都需要使用到一些数据,如:存储返回给用户的登录验证码,可以考虑将该数据存储到session表中,因为request.session取值方便。

django_session表中的数据条数是取决于浏览器的,同一个计算机上(同一个IP地址)、同一个浏览器只会有一条数据生效。当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除,你也可以通过代码清除,主要是为了节省服务端数据库资源。

5.5 Session流程解析

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

5.7 Session版登陆验证

# 登录认证装饰器
from functools import wraps

def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        target_url = request.get_full_path()
        # 请求来时,django中间件利用cookie-->通过sessionid,取出随机字符串-->查询数据库
        # 随机字符串比对上,拿到用户字典--->赋值给request.seesion
        # 登录认证时,利用request.session字典的key(自己定义的),取出用户信息
        if request.session.get('user'):
            return func(request, *args, **kwargs)             
        else:
            return redirect('/login/?next=%s' % target_url)
    return inner


# 登录视图函数
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        user_queryset = models.User.objects.all()
        for user_obj in user_queryset:
            if user_obj.username == username and user_obj.password == password:
                # 1、request.session是个字典,利用字典存值
                # 2、响应走到中间件时,通过判断,再把用户信息这个字典存到数据库/缓存/文件....
                # 3、并自动生成随机字符串,调用cookie,存入浏览器 sessionid:随机字符串
                #  第2、3步操作是django中间件帮你做的
                request.session['user'] = user_obj.username  # 设置session
                target_url = request.GET.get('next')         # 获取跳到登陆页面之前访问的URL
                if target_url:
                    return redirect(target_url)              # 如果有,就跳转回登陆之前的URL
                else:
                    return redirect('/home/')                # 否则默认跳转到home页面
    return render(request, 'login.html')


@login_auth
def home(request):
    return HttpResponse('我是主页,只有登录后才能看到我')

@login_auth
def index(request):
    return HttpResponse('我是index页面,只有登录后才能看到我')

@login_auth
def user_list(request):
    return HttpResponse('我是用户展示页面,只有登录后才能看到我')

# 注销功能
@login_auth
def logout(request):
    request.session.flush()    # 删除所有当前请求相关的session
    return redirect('/login/')

 

posted @ 2022-12-30 22:35  不会钓鱼的猫  阅读(88)  评论(0编辑  收藏  举报