十一:django之cookie与session
前言
我们知道HTTP协议是无状态的,这就意味着,它不会保存用户的登录状态信息。在早期的网页发展过程中,这一特性能够满足绝大数网站的业务需求,诸如,博客、文章,等网站。
随着web技术的日新月异,以及人们的消费能力呈指数式增长,出现了一些如淘宝、京东、拼多多等电商网站,这些网站都具有庞大的客户群体,识别出每一个登录用户并为他们提供支付接口服务是这类网站的主要诉求,这时候,记录用户的登录状态信息(账号名以及密码)显得尤为重要。
cookie技术和session技术的出现,给这类电商网站带来了福音。
cookie技术的核心是:当用户第一次登录后,会将用户的登录状态信息(用户名以及密码)返回给浏览器保存;之后,用户再次访问改网站的时候,浏览器会携带之前的保存的改网站登录信息,这样服务端获取登录信息后可以快速验证登录。
cookie技术虽然能够实现免验证登录的便利(记录用户第一次的登录信息),但是与此同时,用户的一些敏感信息(如,用户密码)也暴露出来。
为了便捷性而牺牲用户数据安全性,这从来不是所有使用者期望看到的,如何寻求一种既便捷而安全的解决方案?
session技术显然能够很好的胜任这一任务。它的核心解决方案是:当用户第一登录后,服务端会生成一个随机的字符串,该字符串在服务端(session)是以{'字符串','用户登录信息'}这种kv键值对形式来保存,与此同时,会将该字符串返回给浏览器保存一份。当用户再次访问时,浏览器会携带者该字符串去后端校验匹配,匹配成功后,则快速登录。
但是,这种方案也不是绝对安全的。如果,通过抓包软件截获了该字符串,还是可以冒充该用户,进行访问。
总结:
(1)cookie:在浏览器(客户端)保存用户登录信息,以kv键值对的形式保存。
(2)session:在服务端保存用户登录信息,也是以kv键值对形式保存,同时会返回给浏览器一份k值(随机字符串)保存在cookie中。
(3)session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)。
补充:
session虽然数据是保存在服务端的 但是禁不住数据量大
"""
token技术:
(1)登陆成功之后 将一段用户信息(敏感信息)使用加密算法进行加密处理;
(2)将加密之后的结果拼接在信息后面,整体返回给浏览器保存;
(3)浏览器下次访问的时候带着该信息,服务端自动切去前面一段信息再次使用自己的加密算法;
(4)跟浏览器尾部的密文进行比对;
"""
jwt认证
三段信息
(后期再讲 结合django一起使用)
Cookie
# 虽然cookie是服务端告诉客户端浏览器需要保存内容
# 但是客户端浏览器可以选择拒绝保存,如果禁止了,那么要是需要记录用户状态的网站登陆功能都无法使用了
cookie基本操作
我们知道,视图函数的返回值是一个HttpResponse对象(render,redirect,JsonResponse等内部实现也是HttpResponse)。我们在操作cookie的时候,需要一个HttpResponse对象,因此这里定义一个变量名用来存储。
res = HttpResponse()
- 设置cookie
res = HttpResponse('surpass')
res.set_cookie('username','surpass777')
# 加盐处理
res = render(request,'index.html',locals())
res.set_cookie('username','surpass777',salt='加密盐')
# 设置超时时间
res = redirect('app01:index',args=(183,))
res.set_cookie('username','surpass777',salt='加密盐',max_age=10)
"""
max_age和expires:
(1)两者都是设置超时时间的,并且都是以秒为单位;
(2)需要注意的是,针对IE浏览器需要使用expires。
"""
- 获取cookie
request.COOKIES.get(key) # 从request中获取cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
"""
default:默认值
salt:加密盐
max_age:超时时间
"""
- 删除cookie(注销功能的实现)
def logout(request):
res = redirect('app01:index',args(1,))
res.delete_cookie('user')
return res
cookie版登陆和注销
需求分析:
(1)登陆认证装饰器,只有登陆的用户才能使用相关的功能;
(2)访问未登录的功能,会重定向到登陆页面;
(3)用户登录成功后,浏览器保存cookie;
(4)用户注销后,浏览器清理cookie信息。
urls.py
urlpatterns = [
url(r'^login/$', views.login, name='login'),
url(r'^home/$', views.home, name='home'),
url(r'^test/$', views.test, name='test'),
url(r'^logout/$', views.logout, name='logout')
]
views.py
from app01 import form
from functools import wraps
# 登录认证装饰器
def login_auth(func):
@wraps(func)
def inner(request, *args, **kwargs):
target_url = request.get_full_path() # /app01/home/ 获取到用户上一次想访问的url
if request.COOKIES.get('username'): # 通过cookie判断用户是否登录
res = func(request, *args, **kwargs)
else:
res = redirect(f'/app01/login/?next={target_url}') # /app01/login/?next=/app01/home/
return res
return inner
def login(request):
login_dict = {'form_obj': form.UserForm()} # 创建空对象,并赋值给form_obj
if request.method == 'POST':
login_dict['form_obj'] = form.UserForm(request.POST)
if not login_dict.get('form_obj').is_valid(): # 如果身份验证没通过
return render(request, 'login.html', login_dict) #将错误信息渲染给html文件
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'surpass' and password == 'wp666a':
next_url = request.GET.get('next') #获取下一次跳转的路径,即上一次用户想访问的路径
if next_url:
response = redirect(next_url)
else:
response = HttpResponse('it is ok')
# 设置cookie
response.set_cookie('username', 'surpass_wp')
else:
response = HttpResponse('用户名或密码错误,登录失败')
return response
return render(request, 'login.html', login_dict)
@login_auth
def home(request):
msg = '我是home页面,欢迎您!'
return render(request, 'home.html', {'msg': msg})
@login_auth
def test(request):
msg = '我是test页面,欢迎您!'
return render(request, 'test.html', {'msg': msg})
@login_auth
def logout(request):
res = redirect('/app01/login/')
res.delete_cookie('username')
return res
login.html
<div class="container-fluid">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h3 class="text-center">登录页面
<span style="color: green" class="glyphicon glyphicon-user"></span>
</h3><br>
<form action="{% url 'app01:login' %}" method="post" novalidate enctype="multipart/form-data">
<table class="table table-striped table-hover table-bordered">
<tbody>
{% for form in form_obj %}
<tr>
<td class="text-center">{{ form.label }}</td>
<td>{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div>
<input type="submit" class="btn btn-success btn-sm form-control" value="登录">
</div>
</form>
</div>
</div>
</div>
Session
cookie 虽然在一定程度上解决了保存用户状态信息的需求,但是其本身存在一些不足,具体如下:
(1)cookie本身最大支持4096字节,无法支持存储更多字节的数据;
(2)cookie是存储在客户端(浏览器),针对一些用户的敏感信息,是具有一定的安全隐患;
因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
session的基本操作
"""
(1)session数据是保存在服务端的,给客户端返回的是一个随机字符串(sessionid);
(2)在默认情况下操作session的时候需要django默认的一张django_session表;
(3)django默认session的过期时间是14天,可以认为修改它;
(4)session是保存在服务端的,但是session的保存位置可以有多种选择:数据库、文件等等;
(5)django_session表中的数据条数是取决于浏览器的,同一个计算机上(IP地址)同一个浏览器只会有一条数据生效。主要是为了节省服务端数据库资源。
"""
- 获取、设置、删除Session中数据
request.session['username'] # 获取session
request.session.get('username',None)
request.session['username'] = 'egon_dsb'
request.session.setdefault('username','jason_dsb') # 存在则不设置
del request.session['username']
- 所有的键、值、键值对
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
- 会话session的key
# 会话session的key
request.session.session_key # 随机字符串ryl3jza580roxfntx8wittr0vykvmi5h
- 将所有失效的失效日期小于当前日期的数据删除
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
- 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
- 删除当前会话的所有Session数据
request.session.delete() # 只删服务端的 客户端的不删
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失效策略。
设置和获取session内部发生的事
- 设置session内部发生的事情
request.session['username'] = 'surpass'
"""
1.django内部会自动帮你生成一个随机字符串
2.django内部自动将随机字符串和对应的数据存储到django_session表中
2.1先在内存中产生操作数据的缓存
2.2在响应结果django中间件的时候才真正的操作数据库
3.将产生的随机字符串返回给客户端浏览器保存
"""
- 获取session内部发生的事情
request.session.get('username')
"""
1.自动从浏览器请求中获取sessionid对应的随机字符串;
2.拿着该随机字符串去django_session表中查找对应的数据;
3.如果比对上了,则将对应的数据取出并以字典的形式封装到request.session中;
如果比对不上,则request.session.get()返回的是None。
"""
session流程解析
session版本登录和注销
urls.py
urlpatterns = [
url(r'^login/$', views.login, name='login'),
url(r'^home/$', views.home, name='home'),
url(r'^index/$', views.index, name='index'),
url(r'^logout', views.logout, name='logout')
]
views.py
from django.shortcuts import render
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
from app01 import form
from functools import wraps
# Create your views here.
# 登录认证装饰器
def login_auth(func):
@wraps(func)
def inner(request, *args, **kwargs):
target_url = request.get_full_path()
if request.session.get('username'):
res = func(request, *args, **kwargs)
else:
res = redirect(f'/app01/login/?next={target_url}')
return res
return inner
def login(request):
login_dict = {'form_obj': form.UserForm()}
if request.method == 'POST':
login_dict['form_obj'] = form.UserForm(request.POST)
if not login_dict.get('form_obj').is_valid():
return render(request, 'login.html', login_dict)
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'surpass' and password == 'wp666ak':
request.session['username'] = 'surpass_wp'
url_next = request.GET.get('next')
if url_next:
res = redirect(url_next)
res = HttpResponse('i am ok')
return res
else:
return HttpResponse('用户相关信息错误!')
return render(request, 'login.html', login_dict)
@login_auth
def home(request):
msg = '我是home页面'
render(request, 'home.html', {'msg': msg})
@login_auth
def index(request):
msg = '我是index页面'
render(request, 'index.html', {'msg': msg})
@login_auth
def logout(request):
request.session.flush()
return redirect('/app01/login/')
login.html
<div class="container-fluid">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h3 class="text-center">登录页面
<span style="color: green" class="glyphicon glyphicon-user"></span>
</h3><br>
<form action="{% url 'app01:login' %}" method="post" novalidate enctype="multipart/form-data">
<table class="table table-striped table-hover table-bordered">
<tbody>
{% for form in form_obj %}
<tr>
<td class="text-center">{{ form.label }}</td>
<td>{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div>
<input type="submit" class="btn btn-success btn-sm form-control" value="登录">
</div>
</form>
</div>
</div>
</div>
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,默认修改之后才保存(默认)
CBV添加装饰器
"""
CBV中django不建议你直接给类的方法加装饰器
无论该装饰器能都正常给你 都不建议直接加
如果想要加需要导入method_decorator模块,用method_decorator给想要加装饰器的函数加装饰器
from django.utils.decorators import method_decorator
"""
在CBV中添加装饰器时,需要:
(1)需要先导入 from django.utils.decorators import method_decorator
(2)在指定方法上方加@method_decorator(method_name)
(3)如果加在类上,还需要提供装饰的名字:@method_decorator(decorator_name, method_name)
- 方式1:给方法加装饰器
from django.views import View
from django.utils.decorators import method_decorator
class MyLogin(View):
@method_decorator(login_auth) # 方式1:给get方法加装饰器
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')
- 方式2:给类加装饰器
from django.views import View
from django.utils.decorators import method_decorator
@method_decorator(login_auth, name='get') # 同时提供装饰器和被装饰的方法
@method_decorator(login_auth, name='post') # 支持装饰器多个叠加
class MyLogin(View):
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')
- 通过dispatch方法给的所有方法加装饰器
from django.views import View
from django.utils.decorators import method_decorator
class MyLogin(View):
@method_decorator(login_auth) # 方式3:它会直接作用于当前类里面的所有的方法
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')