参考博客:博客园-十八岁,原文链接:https://www.cnblogs.com/l199616j/p/11195667.html,感谢先行者们!
一、互联网发展简史:
1.1、需求由来
在很久很久以前,世界上的网站还没有保存用户数据的需求,所有用户访问网站返回的结果都是一样的,比如:新闻、博客、文章,谁来看都是看这些内容。
随着社会的发展,逐渐出现了一些需要保存用户信息的网站,比如购物网站、支付功能等;这些网站的特点就是必须要确认用户的身份来进行一些敏感操作!
以登陆为例,如果不保存用户的登陆状态,也就意味着用户每次访问都要需要输入账号密码,你还想用吗?反正我不想用!
1.2、解决方案
1.2.1、原始方案
当用户第一次登陆成功后,将用户数据返回给浏览器,让浏览器保存下来,之后访问网站的时候浏览器自动将保存在浏览器上的数据发给服务器,服务器获取后拿来验证即可!
不用想都知道早期的这种方式有很大的安全隐患,很容易被窃取数据。
1.2.2、改进方案
当用户登陆成功后,服务端产生随机字符串,用户信息以K/V键值对的形式保存在服务端;然后交由客户端浏览器保存这些随机字符串!格式大约是这样:随机字符串:用户信息
之后访问服务端时,浏览器都带着该随机字符串,服务端去数据库对比是否有对应的字符串从而获取到对应的用户信息!
跟上面的一样,一旦被截获该随机字符串,那么就可以被冒充,所以还是有安全隐患的。
1.3、新时代方案
在web领域没有绝对的安全也没有绝对的不安全,只有不断增强安全性。
cookie
在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于同一个会话的,不能放入用户B或用户C的购物车内,这不属于同一个会话。
而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。
Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。
session
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力总结:
cookie就是保存在客户端浏览器上的信息,session就是保存在服务端上的信息,概念比较模糊;session是基于cookie工作的,大部分的保存用户状态的操作都需要用到cookie。
token
session虽然数据是保存在服务端,但是禁不住数据流太过庞大;因此服务端不再保存数据,而是在登陆成功后,将一段信息进行加密处理,这个加密算法只有你自己知道;将加密后的结果拼接在信息后面,整体返回给浏览器保存
jwt认证
三段信息,后面再讲。
二、Django操作cookie与session
2.1、cookie
Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。
由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
我们知道视图函数的返回值有三种:render, HttpResponse, redirect;如果想要操作cookie,就不得不利用object对象!
2.1.1、设置cookie
def cookie_test(request):
cookie_obj = HttpResponse('我是cookie页面') # 先定义个变量,里面还是视图函数三大返回值。
# 用set_cookie设置cookie,第一个参数为key,第二个参数为value;max_age和expire都是超时时间,后者为老东西IE用!
cookie_obj.set_cookie('cookie_name', 'sanxi666', max_age=600, expires=600)
return cookie_obj # 将对象返回
我们在浏览器右键-检查-application点开cookie可以看到上面配置的cookie
2.2.2、获取cookie
获取cookie合并在登录认证小案例中
用户如果在没有登录的情况下访问一个需要登录的页面,先跳转到登录页面,当认证后应该跳转到用户之前要访问的页面,而不是写死。
def auth(func): # 登录认证装饰器
def inner_func(request, *args, **kwargs):
path = request.path_info # 获取用户想要访问的url
if request.COOKIES.get('sanxi') == 'sanxi666': # 匹配cookie
return func(request, *args, **kwargs) # 匹配则执行该函数
else:
return redirect('/login/?next=%s' % path) # 不匹配则重定向登录页面,并将url传给login的request
return inner_func
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'sanxi' and password == 'sanxi666':
target_path = request.GET.get('next') # 获取装饰器传过来的url,有可能是空的。
if target_path: # 有值则重定向至用户指定页面
user_obj = redirect(target_path) # 将返回内容放进变量名
else: # 无值则默认跳转至首页
user_obj = redirect('/index/') # 将返回内容放进变量名
user_obj.set_cookie('sanxi', 'sanxi666', max_age=600) # 设置cookie
return user_obj # 返回HttpResponse对象
return render(request, 'login.html')
@auth
def index(request):
return HttpResponse('我是index页面')
@auth
def hobby(request):
return HttpResponse('我是hobby页面')
2.2.3、删除cookie
注销功能就需要用到delete_cookie了
@auth
def logout(request):
user_obj = redirect('/login/')
user_obj.delete_cookie('sanxi')
return user_obj
2.2、session操作
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
既然是保存在服务端,那就需要存在Django默认的一张叫django_session表,数据库迁移命令自动创建的一堆表中就有它;Django默认session的过期时间是14天,可以改。
2.2.1、设置session
def login(request):
request.session['sanxi'] = 'sanxi666'
return render(request, 'login.html')
设置session内部发生的事:
- Django内部会自动帮你生成一个随机字符串
- Django内部自动将随机字符串和对应的数据存储到django_session表中,这步不是直接生效的
- 先在内存中产生操作数据的缓存
- 在响应结果到达Django中间件的时候才真正地操作数据库
- 将产生的随机字符串返回给客户端让浏览器保存
django_session表session示意:
这个是实验后select的,因为有很多session,我只保留部分内容。
MariaDB [study]> SELECT * FROM django_session;
+----------------------------------+--------------------------------------------------------------------------------------------------------------+----------------------------+
| session_key | session_data | expire_date |
+----------------------------------+--------------------------------------------------------------------------------------------------------------+----------------------------+
| 0h5ggoqs42n2m9efhd68vitp43qkrmlj | OTA4OGQwZTFjMzM5NjRmNzgxZWY1MmM0OWEyZDIzYzQ5NTBhMjYwMzp7InNhbnhpIjoic2FueGk2NjYifQ== | 2021-03-17 05:02:38.294585 |
+----------------------------------+--------------------------------------------------------------------------------------------------------------+----------------------------+
10 rows in set (0.000 sec)
2.2.2、获取session
def login(request):
request.session['sanxi'] = 'sanxi666'
print(request.session.get('sanxi')) # 获取session
return render(request, 'login.html')
# 执行结果
sanxi666
内部发生的事:
- 自动从浏览器请求中获取session_id对应的随机字符串
- 拿着该随机字符串去Django_session表中查找对应的数据
- 如果匹配上了,则将对应的数据取出并以字典形式封装到request.session中;匹配不上则request.session返回none。
django_session表中的数据条数是取决于浏览器的,同一个计算机上同一个浏览器只会有一条数据生效;主要是为了节省服务端数据库压力,因为一个用户的所有请求都应该属于一个会话!
当session过期的时候可能会出现多条数据对应一个浏览器,这个时间很短暂,会自动清除过期session,也可以手动清除。
2.2.3、设置过期时间
此操作是针对该函数下所有cookie和session的
request.session.set_expiry(3)
括号内可以放四种参数:
- 整数,即多少秒
- 日期对象,到指定日期失效
- 0,一旦当前浏览器窗口关闭立即失效,貌似不行
- 不写,失效时间就取决于Django内部全局session默认的失效时间,就是那个14天。
2.2.4、清除session
- request.session.delete(),只删服务端的,客户端不删
- request.session.flush(),浏览器和服务端都清空,推荐使用
session是保存在服务端的,但是session的保存位置可以有多种选择,数据库、文件、Redis等
2.2.5、session登陆验证
其实就是拿上面的cookie的示例稍微改改就行。
def auth(func):
def inner_func(request, *args, **kwargs):
path = request.path_info
if request.session.get('sanxi') == 'sanxi666': # 把cookie改成session
return func(request, *args, **kwargs)
else:
return redirect(f'/login/?next={path}') # 个人更喜欢用3.X的f而不喜欢%
return inner_func
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'sanxi' and password == 'sanxi666':
path = request.GET.get('next')
request.session['sanxi'] = 'sanxi666' # 设置cookie改成设置session
if path:
path_obj = redirect(path)
else:
path_obj = redirect('/index/')
return path_obj
return render(request, 'login.html')
@auth
def index(request):
return HttpResponse('我是index页面')
@auth
def hobby(request):
return HttpResponse('我是hobby页面')
2.2.6、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失效策略。