cookie与session
# 理论阐述
# cookie和session
cookie
服务端保存在客户端上的信息都可以称作cookie;
它的表现形式一般都是k:v键值对(可以有多个). 即保存在浏览器上的键值对. session
数据是保存在服务端的,并且它的表现形式一般也是k:v键值对(可以有多个).
注意: session是基于cookie工作的(其实大部分保存用户状态的操作都需要用到cookie!)
http协议是无状态的,它不能保存用户信息!
无状态的意思是每次请求都是独立的!它的执行情况和结果与前面的请求和之后的请求都无直接关系.
它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况.
状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留.
会话中产生的数据又是我们需要保存的,也就是说要“保持状态”!
So,产生了一些保存用户信息的技术: cookie、session.
Ps: token不是一门技术,是一种思想、是一种手段.
jwt三段论,可以通过反解得到用户信息.后期会详细介绍!!
★ 发展史
一开始,网站都没有保护用户功能的需求,所有的用户访问返回的结果都一样. eg:新闻、博客等.
往后,出现了交互式网址,一些需要保存用户信息的网站. eg:淘宝、支付宝、京东等.
以登陆功能为例,可以试想下,如果不保存用户登陆状态,意味着用户访问该网站的不同网页都需要重新登陆,用户体验极差!
以购物功能为例,将物品加入购物车,也得记录是哪个用户将该物品加入了购物车.便于下次打开购物车,能展示对应的物品.
那如何保存用户信息呢?
解决技术 -- 会话保持!用cookies来实现.
最初的解决方案是 => cookie:用户数据保存在浏览器中. -- 不安全!
浏览器将 表单信息-用户名和 密码‘浏览器会加密’发送给服务端.
当用户第一次登陆成功后,服务端会将用户用户名和密码返回给浏览器.并让浏览器保存到本地!
之后用户再次访问网站时浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证.
这样服务器就能通过Cookie的内容来判断这个是“谁”了!
安全隐患 -- 用户名和密码保存在本地是可以看到的!
尽管密码是加密的/不是明文,也可以通过撞库破解.或者直接拿到本地的用户名和加密密码模拟浏览器发送请求.
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节;
Cookie本身保存在客户端可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性.
优化后的解决方案
=> session:用户数据保存在服务端. -- 也会依赖于cookie,将随机字符串保存在浏览器中.
★ 这样的话,用户信息没有在客户端,在服务端,提高了安全性!!
当用户登陆成功后,服务端产生一个随机的字符串交给客户端浏览器保存.
eg:在浏览器cookies信息中表现形式如下 # PS:浏览器中的cookies可以简单看着字典,有很多k-v
Name Value
sessionid 随机字符串1 # 该名为sessionid的name值是可以手动设置更改的
而在服务端,也可以以k-v的形式保存用户信息!
eg:在Django的django_session表中的表现形式如下
session_key session_data expire_date
1 随机字符串1 用户1的相关信息 (过期时间)
2 随机字符串2 用户2的相关信息 ---
3 随机字符串3 用户3的相关信息 ---
那么就可以通过随机字符串,得知该登陆凭证是哪个用户啦!!
ps:用户的相关信息应该是加密了的,不管多少,都是256位!!
☆ Django中session的默认失效时间是14天,正好两周!
服务端伪代码:
def login():
username = request.POST.get('username')
password = request.POST.get('password')
# -- 生成随机字符串1 返回给客户端
# -- {'随机字符串1':{'username':username,'password':password}} 后端将其保存到<数据库>中
之后,用户带着该随机字符串访问服务端,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息!!
返回给前端浏览器的随机字符串还是存在于浏览器中的,但凡是存在浏览器中的数据都称之为cookie!!
安全隐患 -- 若攻击者截获了该随机字符串,就可以冒充当前用户.
So,在web领域没有绝对的安全,也没有绝对的不安全.
防御措施:https、更改默认的session_id的名字等 https://cloud.tencent.com/developer/article/1043362
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Q: session的用法是基于cookie的!! 若用户在浏览器上禁用了cookie的使用,那么还能用session吗?
A: 禁用了cookie就不能通过cookie传递随机字符串啦,那么我们可以再找个地方把随机字符串存起来,通过其它方式传递给后端
一般来说,将随机字符串传递给后端的方式有两种:(要改动session相关的源码..)
1> 将随机字符串放到url地址后面, 以参数的形式传递过去
2> 将随机字符串放到请求头里传递过去.
# token
session的用户数据是保存在服务端的,经不住数据量大带来的影响!
利用token的思想,可以用CPU计算时间获取服务端session 存储空间!!
此处阐述的是 jwt token的原理!
登陆成功后,服务端将 <一段用户信息>(不会包含密码等敏感信息) 进行加密处理 -- 加密算法只有公司的开发知道.eg:rc4算法并加盐.
将加密后的结果(称之为令牌)拼接到 <那一段用户信息> 后面,整体返回给浏览器,浏览器将其作为cookie进行保存!
浏览器下次访问服务端时带着该cookie信息
服务端接收后,分割出<那一段用户信息>并进行同样的加密处理,将结果与分割剩下的那部分进行比对!!!
2
3
4
5
# cookie的使用
cookie是服务端告诉客户端浏览器需要保存的内容
若客户端浏览器选择拒绝保存, 禁用cookie, 那么需要记录用户状态的网站的登陆功能就无法使用啦!!后端视图在响应头中设置响应头, 浏览器将响应头中的cookie保存, 下次请求将cookie放到请求头中发送给后端..
需求:
1> 用户登陆成功后,浏览器保存cookie信息,并设置过期时间;
2> 网站的某些页面需要登陆后才能访问;
3> 用户访问一个需要登陆的页面,跳转登陆页面登录成功后,需要跳转到用户之前需要访问的页面!!
4> 注销功能, 主动删除cookie.
以订单页面为例,需要登陆,跳转到该页面!
登陆成功后,浏览器也将cookie保存成功!会跳转会订单页面!
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
url(r'^home/', views.home),
url(r'^order/', views.order),
url(r'^logout/', views.logout),
]
2
3
4
5
6
7
8
9
10
11
12
views.py
"""
cookie:
1.无状态
2.最常用于保存用户信息
3.cookie保存的位置 -- 客户端浏览器
k:v键值对
可以存储多个
不安全,因为存储用户信息的cookie在客户端
4.设置cookie
必须基于HttpResponse对象
obj = HttpResponse()
obj = redirect()
obj.set_cookie('k','v', max_age=10)
obj.set_cookie('k1','v1')
5.获取cookie
request.COOKIES.get('k')
6.删除cookie
set_cookie(key, value='', max_age=None, path='/', domain=None, httponly=False, secure=False)的一些参数:
1> max_age表示浏览器保存这个cookie多少秒,多久失效 None表明浏览器关闭,cookie就没了.
2> path 值为'/' 表示该网站下的所有路径都可以访问. '/login' --> '/login/auth/'可以; '/order/'不行.
3> domain=None Cookie生效的域名
设置为domain=.dc.com v1.dc.com可以;v2.dc.com可以
设置为domain=v1.dc.com v1.dc.com可以;v2.dc.com不可以
4> secure 值为True,表明只有https协议的请求才会携带cookie
5> httponly 值为True,表明浏览器不能通过js获取到cookie, document.cookie的值为空.
"""
from django.shortcuts import render, HttpResponse, redirect
# -- 验证登陆的装饰器. (一个网站有很多页面需要登陆后才能访问!)
def login_auth(func):
def inner(request, *args, **kwargs):
# -- request.COOKIES.get('username')没有的话,返回的是None.
if not request.COOKIES.get('username'):
# -- 没有cookies,需要登陆,登陆成功后,跳转到之前需要登陆的页面!!
# 以订单页面为例
# 1> 访问http://127.0.0.1:8000/order/
# target_url的值为/order/
# 2> 没有cookie,跳转http://127.0.0.1:8000/login/?next=/order/
# 3> 在登陆方法里,request.GET.get('next')取到/order/,登陆成功后跳转!
target_url = request.get_full_path()
return redirect('/login/?next=%s' % target_url)
return func(request, *args, **kwargs)
return inner
def login(request):
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
# print(target_url) # /order/
if username == 'ly' and password == '123':
target_url = request.GET.get('next')
# -- ★★★ 大前提:要想使用cookie,必须在返回HttpResponse的基础上使用!!
# render和redirect的本质也是HttpResponse!!
# eg: obj = HttpResponse('login success!')
# -- target_url也有可能为空.需要判断!!
if target_url:
obj = redirect(target_url)
else:
obj = redirect('/home/')
# -- cookie的保存形式是一个字典!
# 1> set_cookie的第一个参数代表key,第二个参数代表value.
# 2> 也可以设置cookie的失效时间!!
# max_age和expires参数两者都是设置超时时间的!并且都是以s秒为单位!
# 针对IE浏览器,需要使用expires参数!
# -- 当然可以设置多个cookie!!
# obj.set_cookie('username1', 'egon')
# obj.set_cookie('username2', 'dc')
obj.set_cookie('username', username, max_age=3600) # -- 设置cookie
# print(request.COOKIES.get('username')) # -- 获取cookie
return obj # -- 这行代码执行完后,浏览器才会记录下cookie信息
else:
return HttpResponse('login fail!')
return render(request, "login.html", locals())
@login_auth
def home(request):
# -- 我们设定首页需要登陆后才能访问!!
# 判断,若没有对应的cookie,返回的是None
# if not request.COOKIES.get('username'):
# return redirect('/login/')
return HttpResponse("这是home首页!!")
@login_auth
def order(request):
# if not request.COOKIES.get('username'):
# return redirect('/login/')
return HttpResponse("这是订单页面!!")
@login_auth
def logout(request):
# -- 注销功能,删除cookie
obj = redirect('/login/')
obj.delete_cookie('username')
# obj.delete_cookie('username1')
return obj
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Title</title>
</head>
<body>
<div style="padding: 20px">
<form action="" method="post">
<p>username: <input type="text" name="username"></p>
<p>password: <input type="password" name="password"></p>
<input type="submit" class="btn">
</form>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
扩展: cookie不单单可以用来保存用户信息, 业务逻辑上需要保存到都可以使用cookie!!
举个例子 -- 密码输入三次锁定,如何判断输入错误三次?
obj.set_cookie['error_count'] = request.COOKIES.get('error_count') + 1
若error_count这个cookie对应的值为3, 立马锁定!!
# session的使用
cookie暴露用户信息, 长度也有限.
session数据是保存在服务端的!
给客户端返回的是一个随机字符串. 客户端将其作为cookie存储,存储的形式为 -- sessionid:随机字符串
客户端只有cookie的值,没有用户信息!!
后端, 随机字符串:用户相关的一些信息.在 默认 情况下,Django操作session会使用Django迁移数据库时默认生成的 django_session 表.
当然session的保存位置可以有其它的多种选择,eg: mysql、redis、文件...
# 存储到文件中
# -- 准备工作:
在项目根目录下创建文件夹xxxx
确保session中间件没被注释, 'django.contrib.sessions.middleware.SessionMiddleware'
这样的话,在配置文件里设置的那些session就能生效.会在中间件里处理.
不需要session对应的app,可注释掉.
# -- 在setting文件中进行一下配置
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = "xxxx" # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
# 若有值,设置session后会在该文件夹下生成以随机字符串命名的session文件,一个用户对应一个.
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 = True # 值为True,表明session过期时间为当前访问往后推两周,每次访问都会更新.
# -- 在视图函数中进行以下操作
def login(request):
# !在session中设置值 + cookie中写入凭证
# --> sessionid:随机字符串 随机字符串:{'username':'wupeiqi'} 加密了的.
request.session['username'] = 'wupeiqi'
return HttpResponse("hello")
def login(request):
# !读取请求的cookies中键为sessionid的值,即随机字符串 + 读取该字随机符串对应的数据
username = request.session.get("username")
print(username) # 'wupeiqi'
return HttpResponse("world")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# session to mysql
# -- 准备工作:
确保session中间件没被注释, 'django.contrib.sessions.middleware.SessionMiddleware'
这样的话,在配置文件里设置的那些session就能生效.会在中间件里处理.
确保session对应的app没被注释, 'django.contrib.sessions'
记得迁移数据库,在数据库中生成session对应的表!!
# -- 在setting文件中进行一下配置
# /.venv/lib/python3.8/site-packages/django/conf/global_settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 可以去Django内部默认的setting配置就是这个!!!
SESSION_FILE_PATH = None
SESSION_COOKIE_NAME = "sessionid"
SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 1209600
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
简单说说经历了哪些事情..
★ request.session['username'] = 'egon' 设置session经历了哪些事?!
1> Django内部生成一个随机字符串
2> Django操作数据库,将随机字符串和加密后的session数据存储到django_session表中
2.1 先在内存中产生操作数据的缓存
2.2 服务端 返回浏览器数据/响应 时,才会通过session相关中间件真正的操作数据库
3> 把随机字符串返回给浏览器,浏览器以sessionid作为key,随机字符串作为value!
★ request.session['username'] 获取session经历了哪些事?!
1> 自动从浏览器的请求中获取sessionid对应的随机字符串 是在session中间件里做的.
2> 拿到随机字符串后,去django_session表中查询session数据.
sql语句: select * from table where session_key = '随机字符串'
若查询不到,返回None值!!
3> 如果查询结果有值,Django会把session数据取出来并解密,然后以字典的形式封装到request.session中.
这样,俺们才能从request.session中取出数据.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
views.py
先访问网址 http://127.0.0.1:8000/set_session/
, 再访问 http://127.0.0.1:8000/get_session/
顺序颠倒的话, 会报错!!因为先 /get_session/ 的话, django_session表中还没有相关的session!! 也就取不到里面的值!!
def set_session(request):
# -- 设置session
# 这多个session都通过加密混到了django_session表的session_data字段中!
request.session['username'] = 'egon'
request.session['username1'] = 'egon1'
request.session['username2'] = 'egon2'
request.session['username3'] = 'egon3'
# -- 设置session过期时间 括号里可以放四种类型的参数
# 1.整数 单位秒
# 2.日期时间 到指定时间失效
# 3.0 一旦浏览器窗口关闭就失效
# 4.不写 失效时间取决于Django内部全局session默认失效时间
request.session.set_expiry('')
return HttpResponse('ok')
def get_session(request):
# -- 获取session
print(request.session['username']) # egon
print(request.session['username1']) # egon1
print(request.session['username2']) # egon2
return HttpResponse('ok')
def del_session(request):
# -- 删除session
# request.session.delete() -- 只删服务端的,客户端的不删
# request.session.flush() -- 浏览器和服务端都清空(推荐使用)
request.session.flush()
return redirect('/login/')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
session版本的登陆验证, 参考链接: https://www.cnblogs.com/Dominic-Ji/p/10718365.html
小实验
django_session表中存储的session数据条数取决于有多少个浏览器!!
做个小实验!!
step1: 后端设置了3条session信息,用谷歌发送请求后,表中会有一条信息.
step2: 后端再添加一条session信息,用谷歌再次发送请求后,表中还是只会有一条信息.
session_key的值不会变,session_data的值会有所变化!!
step3: 接着用火狐发送请求后,表中会多一条信息.
Ps:当session过期时,可能会出现多条数据对应同一个浏览器,但该现象不会持续很久,内部会自动识别过期的数据进行删除.
# session to redis
手动的让Django连接redies作缓存之用.. 可以让session放到redies缓存中..
step1: 启动redis服务 参考: https://pythonav.com/wiki/detail/10/82/
step2: pip install django-redis
step3: 在settings中进行以下配置 - session中间件需要,session对应的app可不要.
CACHES = {
"default": { # -- ▲ 这里default
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 100,
"encoding":"utf-8",
}
# "PASSWORD": "密码",
}
}
}
# session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default' # -- ▲ 这里default
SESSION_COOKIE_NAME = "sid"
SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 1209600
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
step4: 手动操作redis
from django_redis import get_redis_connection
conn = get_redis_connection("default")
conn.set("xx","123123",ex=10)
conn.get("xx")
2
3
4
5
# CBV添加装饰器
一共有三种方式!! 根据需求任选其一即可!
实验效果:
直接访问http://127.0.0.1:8000/train/
, 会跳转到http://127.0.0.1:8000/login/
, 显示“登陆页面'';
访问http://127.0.0.1:8000/set_cookie/
后,再访问http://127.0.0.1:8000/train/
, 页面不会跳转,直接显示“train_get”.
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^train/', views.Train.as_view()),
url(r'^set_cookie/', views.set_cookie),
url(r'^login/', views.login),
]
2
3
4
5
6
7
8
9
10
11
views.py
from django.shortcuts import HttpResponse, redirect
from django.utils.decorators import method_decorator # -- !!!
from django.views import View
def login_auth(func):
def inner(request, *args, **kwargs):
if request.COOKIES.get('username'):
return func(request, *args, **kwargs)
else:
return redirect('/login/')
return inner
def set_cookie(request):
obj = HttpResponse('set_cookie hello!')
obj.set_cookie('username', 'ly')
return obj
def login(request):
return HttpResponse('登陆页面!')
# -- ★ CBV添加装饰器第一种方式!!
class Train(View):
@method_decorator(login_auth)
def get(self, request):
return HttpResponse('get')
def post(self, request):
return HttpResponse('post')
# -- ★ CBV添加装饰器第二种方式!!
@method_decorator(login_auth, name='get')
@method_decorator(login_auth, name='post')
class Train(View):
def get(self, request):
return HttpResponse('train_get')
def post(self, request):
return HttpResponse('train_post')
# -- ★ CBV添加装饰器第三种方式!!
class Train(View):
# -- 请求过来后会先执行dispatch()这个方法!
# 所以这里相当于给所有的方法都添加装饰器!!
@method_decorator(login_auth)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse('train_get')
def post(self, request):
return HttpResponse('train_post')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59