【2022-09-09】Django框架(九)

语言: CN / TW / HK

Django框架(九)

cookie与session简介

网址的发展史:
        1、起初网站都没有保存用户功能的需求,所有用户访问返回的结果都是一样的。
        比如:新闻网页,博客网页,小说... (这些网页是不需要登录后才能访问的,每个人访问的结果都一样)
        2、后来出现了一些需要保存用户信息的网站
        比如:支付宝,淘宝,京东.... (用户登录后只要不长时间不访问就不会退出登录)

举例

以登录功能为例:
 
# 如果不保存用户的登录状态,也就是意味着用户每次访问都需要重复的输入,用户名和密码,甚至于如果用户从该地址点击某连接,跳转到另一个子网页,也需要重复的输入用户名和密码,如果页面卡了,刷新页面也可能需要重新登录,输入用户名和密码。(这样的页面用户还会用吗?)
 
那么这时开发者们就想到了一个解决方案:
# 当用户第一次登录成功之后,将用户的用户名密码返回给用户浏览器,让用户浏览器保存在本地,之后用户再次访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证
# 但是早期这种方式具有非常大的安全隐患(因为这中方式是铭文保存的,完全可以被别人找到看到)
 
 
优化:
# 当用户登陆成功之后,服务端随机产生一个随机字符串(在服务端保存数据,用k:v键值对的形式),交由客户端浏览器保存。之后访问服务端的时候,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息。
# 但是如果有人截获到了某用户的该随机字符串,那么就可以冒充他,其实也是有安全隐患的
 
 
# 在web领域是没有绝对的安全也没有绝对的不安全的。

什么是cookie?

cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户的状态,就使用response向客户端浏览器颁发一个cookie。客户端浏览器会把cookie保存起来。当浏览器再次请求该网站时,浏览器就会把请求地址和cookie一同给服务器。服务器检查该cookie,从而判断用户的状态。服务器还可以根据需要修改cookie的内容。
 
# 表现形式:
	k:v键值对(可以存多个)

什么是session?

session是另一种记录客户状态的机制。不同的是cookie保存在客户端浏览器中,而session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是session。客户端浏览器再次访问时只需要从该session中查找该客户的状态就可以了。 如果说cookie机制是通过检查客户身上的“通信证”,那么session机制就是通过检查服务器上的“客户明细表”来确认客户身份。
# 表现形式
数据时保存在服务端的并且他的表现形式一般也是k:v键值对(可以有多个)
 
 
1.cookie就是保存在客户端浏览器上的信息
2.session就是保存在服务端上的信息
3.session是基于cookie工作的。(大部分的保存用户状态的操作都需要使用到cookie)

了解了cookie与session的工作原理,接下来我们来看他们具体是怎么使用的。

cookie操作

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


# 如果在浏览器上 >> 隐私设置和安全性 >> Cookie及其他网站数据 >> 阻止了cookie 
# 那么此时所有需要记录用户状态的网站的登录功能都无法使用,因为网站无法保存服务端发来的用户名密码,永远无法校验用户名和密码,这样就无法登录进去

删除当前页面的cookie

# 我们可以在页面上手动删除我们的cookie,这样浏览器就没有记录我们的登录信息,服务端无法验证,这样就需要我们重新登录。

实操

# 我们之前操作视图函数的返回值
return HttpResponse()
return render()
return redirect()
 
# 那么如果想要操作cookie就需要我们这样编写:
obj1 = HttpResponse()
# 操作cookie
return obj1
 
obj2 = render()
# 操作cookie
return obj2
 
obj3 = redirect()
# 操作cookie
return obj3
# 如果你想要操作cookie,你就不得不利用obj对象
# cookie关键字:
设置cookie:obj.set_cookie(key,value)   # 需要使用到上述提到的对象
	加盐处理:obj.set_signed_cookie(key,value,salt='盐')
获取cookie:request.COOKIES.get('key')
	加盐数据获取:request.get_signed_cookie(key,salt='盐')

示例

# 我们来看一个真正的登录功能
 
def login(request):
    if request.method == 'POST':   # 查询post请求数据
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'gary' and password == '123':
            return redirect('/home/')   # 如果用户输入的匹配就跳转到home页面
    return render(request,'login.html')
 
 
def home(request):
    return HttpResponse('我是home页面,只有登陆的用户再能进来')

# 但是上述存在一个问题:我们需求是必须输入正确的用户名密码才可以进入home页面,但是我们直接访问/home/路由也可以直接进入home页面对吧。那么这就就需要用到cookie

优化

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'gary' and password == '123':
**************************应用cookie区域*************************************
            obj = redirect('/home/')  # 用户名密码正确跳转到home主页
            # 让浏览器记录cookie数据
            obj.set_cookie('username','gary222')  # 这里就可以随便放一个键值对,可以直接放一个用户的用户名名也可以,目的是为了让客户端浏览器保存
            # 浏览器不单单的帮你存这个键值对
            # 每次访问的时候还会带着他过来进行验证
            return obj
*****************************************************************************
 
    return render(request,'login.html')
 
 
def home(request):
    # 获取cookie信息 判断是否有cookie
    if request.COOKIES.get('username') == 'gary222':  # 只有携带cookie才可以进入home页面
        return HttpResponse('我是home页面,只有登陆的用户再能进来')
    # 如果没有登录则跳转到登陆页面
    return redirect('/login/')

# 这样我们想要直接访问home页面就不允许了,必须登录之后才能访问,并且登录之后会记录登录状态,下次再次直接访问home页面也是可以访问的。

不足之处

# 不足之处1:
# 现在我们只有一个home页面,那么如果有很多页面呢,是不是在视图函数很多的时候都要做一次判断,判断是否存在cookie,判断是否已经登陆,那么此时我们应该在每个也面前加一个校验用户是否已经登陆的装饰器。
 
# 不足之处2:
# 比如:
	用户访问index页面,然后跳转到login登录页面进行登录,但是当用户登录后,此时跳转的还是home页面,这样是不合理的。需要的是用户访问什么页面,登陆后跳转的就是用户想要的页面,而不是主页面。

实现不足之处1:

# 增加验证是否登录的装饰器:
def login_auth(func):  # 此时func就是home函数
    def inner(request,*args,**kwargs):
        if request.COOKIES.get('username'):  # 判断cookie是否有值
            res = func(request,*args,**kwargs)  # 有值则执行对应函数
            return res
        else:  # 没有cookie值则跳转登录页面
            return redirect('/login/')
    return inner
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'gary' and password == '123':
            obj = redirect('/home/')
            # 让浏览器记录cookie数据
            obj.set_cookie('username','gary222')  # 这里就可以随便放一个键值对了,可以直接放一个用户的用户名名也可以
            # 浏览器不单单的帮你存这个键值对
            # 每次访问的时候还会带着他过来
            return obj
    return render(request,'login.html')
 
 
@login_auth  # 添加语法糖
def home(request):
    return HttpResponse('我是home页面,只有登陆的用户再能进来')
 
@login_auth
def index(request):
    return HttpResponse('我是index页面')
 
@login_auth
def shop(request):
    return HttpResponse('我是shop页面')
 
# 此时就解决了多个页面代码冗余的问题。

实现不足之处2:

"""
用户如果在没有登陆的情况下想访问一个需要登陆的页面
那么先跳转到登陆页面 当用户输入正确的用户名和密码之后
应该跳转到用户之前想要访问的页面去 而不是直接写死
"""
 
# 访问登陆页面就两种情况:
	要么是直接访问登陆页面的,要么是通过装饰器跳转到登录页面的
 
补充:获取当前用户请求的url
# print(request.path_info)  # 该方法不获取路由后面的参数
# print(request.get_full_path())  # 能够获取到用户上一次想要访问的url(上一次访问的就是跳转到login页面之前想要访问的页面)(同样可获取到参数)
def login_auth(func):
    def inner(request,*args,**kwargs):
        target_url = request.get_full_path()  # 获取用户想要访问的url
        if request.COOKIES.get('username'):
            res = func(request,*args,**kwargs)
            return res
        else:
            return redirect('/login/?next=%s'%target_url)
        # 这样跳转到登录页面后,url后面会携带(?next='上一次用户访问的url页面')的参数
    return inner

# 那么此时就可以通过request.GET的方法拿到后面的参数,然后指定下一次跳转的页面
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'gary' and password == '123':
*****************************************************************
            target_url = request.GET.get('next')  # 这个结果可能是none(可能用户直接访问login)
            if target_url:  # 如果参数有指
                obj = redirect(target_url)   # 则跳转到指定路由的页面
            else:
                obj = redirect('/home/')    # 如果用户直接访问的是登录页面则返回指定的home页面
*****************************************************************
            obj.set_cookie('username','gary222') 
            return obj
    return render(request,'login.html')

参数补充

1、# 可以设置超时时间:cookie可以存在多长时间,过了时间就自动清除cookie,不保存登录状态则下次需要重新登录。
 
参数:
max_age=时间限制(以秒为单位)
expires=时间限制(以秒位单位)
两者区别:
在设置cookie的时候可以添加一个超时时间
	obj.set_cookie('username', 'jason666',max_age=3,expires=3)
	max_age
	expires
		两者都是设置超时时间的 并且都是以秒为单位
		需要注意的是 针对IE浏览器需要使用expires
        
        
2、主动删除cookie(类似于退出登录/注销功能)
@login_auth   # 注意退出登录也是登录后才能操作的所以需要添加装饰器
def logout(request):
    obj = redirect('/login/')
    obj.delete_cookie('username')
    return obj

session操作

# 设置session
request.session['key'] = value
# 获取session
request.session.get('key')
 
# session数据是保存在服务端的,给客户端返回的是一个随机字符串的形式。
# 不是(key:value)的形式
# 而(sessionid:随机字符串)的形式

设置session

urls.py

# 设置session
    url(r'set_session',views.set_session)

views.py

def set_session(request):
    request.session['hobby'] = 'girl'   # 设置session给前端返回一个随机字符串
    return HttpResponse('hello 小姐姐!')
 
# 访问:

# 上述情况是因为什么呢?
# 这是因为,上述提到session的数据是保存在服务端的,那保存到那里了呢?
所以需要给session一个保存数据的地方,在默认情况下操作session的时候需要django默认的一张django_session表。
我们是否还记得,在做数据库迁移命令的时候,会自动创建出很多我们不认识的表,那么这里就有我们需要的django_session表。
 
# 数据库迁移命令:
       makemigrations
       migrate

再次访问

# 过期时间:
	django默认的session过期时间是14天,但是也可以认为的修改它。

获取session

def get_session(request):
    print(request.session.get('hobby'))
    return HttpResponse('下次再见!')

# 设置session内部发生了那些事:
    1.django内部会自动帮你生成一个随机字符串
    2.django内部自动将随机字符串和对应的数据村粗带django_session表中
    3.将产生的随机字符串返回给客户端浏览器保存
    
# 获取session内部发生的那些事:
    1.自动从浏览器请求中获取sessionid对应的随机字符串
    2.拿着该随机字符串去django_session表中查找对应的数据
    3.如果比对上了,则将对应的数据(session_data)取出并以字典的形式封装到request.session中
      如果比对不上,则request.session.get()返回None

研究:如果设置多个session

def set_session(request):
    request.session['hobby'] = 'girl'
    request.session['hobby1'] = 'girl1'
    request.session['hobby2'] = 'girl2'
    request.session['hobby3'] = 'girl3'
    return HttpResponse('hello 小姐姐!')
 
# 可同时设置多个session,但是只占用一条记录

# 并且取得时候都可以取到。
 
def get_session(request):
    print(request.session.get('hobby'))
    print(request.session.get('hobby1'))
    print(request.session.get('hobby2'))
    print(request.session.get('hobby3'))
    return HttpResponse('下次再见!')

总结

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

设置过期时间

# 格式:request.session.set_expiry()
 
	括号内可以放四种类型的参数
        1.整数		  多少秒
        2.日期对象		到指定日期就失效
        3.0			   一旦当前浏览器窗口关闭立刻失效
        4.不写		 失效时间就取决于django内部全局session默认的失效时间

清除session

# 清除session	
	request.session.delete() 	 # 只删服务端的 客户端的不删
	request.session.flush() 	 # 浏览器和服务端都清空(推荐使用)

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

Django中间件

什么是Django中间件

Django中间件相当于Django得门户:
1.请求来的时候需要先经过中间件才能到达真正的django后端
	(浏览器给后端发送请求必须经过中间件)
2.响应走的时候最后也需要经过中间件才能发送出去
	(后端给浏览器返回数据的时候也需要经过中间件)
    
# Django自带7个中间件

Django中间件代码

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
 
# 我们来看这7个中间件:他们其实并不是一个字符串,他们其实是一个模块的路径
 'django.contrib.sessions.middleware.SessionMiddleware'
相当于:
from django.contrib.sessions.middleware import SessionMiddleware

我们来看一下这几个中间件有什么规律:

Django支持程序员自定义中间件而且暴露给程序员五个可以自定义得方法:
1.常用:
	process_request
   	process_response
2.了解:
	process_view
    process_template_response
    process_exception

如何自定义中间件

第一步:在项目名或者应用名下创建一个任意名称的文件夹
第二步:在该文件夹内创建一个任意名称的py文件
第三步:在该py文件内需要书写类(这个类必须继承MiddlewareMixin)
		然后在这个类里面就可以自定义五个方法了
		(这五个方法并不是全部都需要书写,用几个写几个)
第四步:需要将类的路径以字符串的形式注册到配置文件中才能生效
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    '你自己写的中间件的路径1',
    '你自己写的中间件的路径2',
    '你自己写的中间件的路径3',
]

我们根据上面的步骤来自定义中间件来研究这5个方法

process_request

自定义的mymidd.py

# 需要导入模块来继承该模块
from django.utils.deprecation import MiddlewareMixin
 
class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个自定义中间件里得process_request方法')
 
class MyMiddleware2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第二个自定义中间件里得process_request方法')

setting.py

# 注册自己的中间件(在应用下创建的,路径会有提示,但是如果在项目下创建的就没有提示了)
    MIDDLEWARE = [
    'app01.mymiddleware.mymidd.MyMiddleware1',
    'app01.mymiddleware.mymidd.MyMiddleware2'  
        ]

views.py

def index(request):
    print('我是视图函数index')
    return HttpResponse('index')

我们在把中间件注册位置换一下看看打印是什么结果:
 MIDDLEWARE = [
    'app01.mymiddleware.mymidd.MyMiddleware2',
    'app01.mymiddleware.mymidd.MyMiddleware1'  
        ]

# 我们给自定义的中间件返回一个HttpResponse对象:
class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第1个自定义中间件里得process_request方法')
        return HttpResponse('我是第1个自定义中间件里得process_request方法的返回值')

总结process_request:

1.请求来的时候需要经过每一个中间件里面的process_request方法
结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
而是直接原路返回(校验失败不允许访问...)
 
# process_request方法就是用来做全局相关的所有限制功能

process_response

class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第1个自定义中间件里得process_request方法')
 
    def process_response(self,request,response):
        print('我是第1个自定义中间件里得process_response方法')
        return response
class MyMiddleware2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第2个自定义中间件里得process_request方法')
 
    def process_response(self,request,response):
        print('我是第2个自定义中间件里得process_response方法')
        return response

1.响应走的时候需要结果每一个中间件里面的process_response方法
该方法有两个额外的参数request,response
2.该方法必须返回一个HttpResponse对象
    1.默认返回的就是形参response
    2.你也可以自己返回自己的
3.顺序是按照配置文件中注册了的中间件从下往上依次经过
	如果你没有定义的话 直接跳过执行下一个

研究如果response返回自己的HttpResponse回事怎样的结果:

class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第1个自定义中间件里得process_request方法')
 
    def process_response(self,request,response):
        print('我是第1个自定义中间件里得process_response方法')
        return HttpResponse('我是中间件1')
class MyMiddleware2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第2个自定义中间件里得process_request方法')
 
    def process_response(self,request,response):
        print('我是第2个自定义中间件里得process_response方法')
        return response
 
 
# 结果:

# 研究如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况
 
class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第1个自定义中间件里得process_request方法')
        return HttpResponse('中间件1request')   # 返回HttpResponse
 
    def process_response(self,request,response):
        print('我是第1个自定义中间件里得process_response方法')
        return response
 
class MyMiddleware2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第2个自定义中间件里得process_request方法')
 
    def process_response(self,request,response):
        print('我是第2个自定义中间件里得process_response方法')
        return response

process_view

# 具体使用:
def process_view(self,request,view_name,*args,**kwargs):
    print(view_name,args,kwargs)
    print('我是第二个自定义中间件中的process_view方法')
 
# 执行顺序:
路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该方法
顺序是按照配置文件中注册的中间件从上往下的顺序依次执行		
 
# 所以在视图函数提交之前需要添加额外的操作可以在这个方法里做。

process_template_response

返回的HttpResponse对象有render属性的时候才会触发
顺序是按照配置文件中注册了的中间件从下往上依次经过

process_exception

当视图函数中出现异常的情况下触发
顺序是按照配置文件中注册了的中间件从下往上依次经过