1204 中间件以及cookie,session
一 .cookie与session原理
为什么会有这些技术
- 目的:为了保存客户端的用户状态
- 原因: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
1.1 设置cookie set_cookie
告诉浏览器设置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
只获取urlrequest.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流程解析
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天 两周
django session在创建数据的时候,是针对浏览器,多条数据多个浏览器
执行的发生顺序
- django内存自动帮你调用算法生成了一个随机的字符串
- 在django_session数据库表中添加数据(数据也是加密处理)
随机字符串 加密之后的数据 失效时间
sfdfs dsdfsadfsdf 推延2周
-
将产生的随机字符串返回给客户端浏览器,让浏览器保存
以
sessionid:随机字符串
形式保存
2.2 获取session session.get
request.session.get('k')
发生的顺序
- django内部会自动去请求头里面获取cookie
- 拿着sessionid所对应的随机字符串去django_session表中一一对比
- 如果比对成功,会将随机字符串对应的数据取出来,自动放入
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 在两个机器之间搬来搬去, 快累死了。
后来有个叫Memcached的支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了, 所有人都得重新登录一遍, 估计得被人骂死。
也尝试把这个单点的机器也搞出集群,增加可靠性, 但不管如何, 这小小的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了。
这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
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流程图
2. 中间件定义
中间件是在request
和response
处理过程中的一个插件。
比如在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对象),前端就能获得什么
3. process_view
def process_view(self,request,view_name,*args,**kwargs):
print('我是第一个中间件里面的process_view方法')
- 路由匹配成功之后执行,视图函数触发之前
- 如果该方法返回了HttpResponse对象 那么会从下往上一次经过每一个中间件里面的process_response方法
4.process_exception
def process_exception(self,request,exception):
username
print('我是第一个中间件process_exception方法')
print(exception)
- 视图函数中出现错误时,会自动触发,顺序是从下往上
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)