一、会话跟踪
1.什么是会话
会话,可以理解为客户端与服务器之间的一次会晤,在一次会晤中可能包含多次的请求和响应,直到这次会晤结束,这整个过程就是一次会话。
比如你访问浏览器,浏览器客户端向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。
在一个会话中的多个请求中共享数据,就是会话跟踪技术,例如:
- 请求银行主页;
- 请求登录(请求参数是用户名和密码);
- 请求转账(请求参数与转账相关的数据);
- 请求信誉卡还款(请求参数与还款相关的数据)。
在上述会话中,当前用户信息在这次会话中是共享的,登录的人和转账以及还款的人一定是相同的,也就是再一次会话中我们要实现数据共享的能力,**但是我们知道http协议是无状态和无连接的,**这个时候就需要别的机制来实现,也就是cookie技术。
二、cookie
1.Cookie的简介
我们知道HTTP协议是无状态的。
所谓无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
也就是说浏览器的请求对服务器来说,每次都是新的。
状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。
2.cookie工作原理
cookie是浏览器的技术,Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
cookie的工作原理是:浏览器访问服务端,带着一个空的cookie,然后由服务器产生内容,浏览器收到相应后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断请求者是“谁”。
cookie的查看方式
使用chrome浏览器,F12打开开发者工具,查看cookie
cookie的工作图解
- Cookie大小上限为4KB;
- 一个服务器最多在客户端浏览器上保存20个Cookie;
- 一个浏览器最多保存300个Cookie,因为一个浏览器可以访问多个服务器。
Cookie与HTTP头
Cookie是通过HTTP请求和响应头在客户端和服务器端传递的:
- Cookie:请求头,客户端发送给服务器端;
- 格式:Cookie: a=A; b=B; c=C。即多个Cookie用分号离开; Set-Cookie:响应头,服务器端发送给客户端;
- 一个Cookie对象一个Set-Cookie: Set-Cookie: a=A Set-Cookie: b=B Set-Cookie: c=C
Cookie的覆盖
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。
例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。
关于cookie理解:
一个浏览器客户端并一个服务端对应一组cookie,多账号会覆盖。
意思是说,如果你使用同一个浏览器,登录一个帐号访问一个网址,只有一组cookie,如果你登录另外一个帐号访问这个网址,那么也只能存在一组cookie,后面登录的cookie会把之前的cookie顶替掉。
3.Django中操作cookie
Ctrl + Shift + del三个键来清除页面缓存和cookie,将来这个操作你会用的很多
获取cookie方法
request.COOKIES['key'] # 通过键获取cookies中的某个值
request.COOKIES.get('key') # 推荐
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数说明:
- default: 默认值
- salt: 加密盐
- max_age: 后台控制过期时间
设置cookie方法
cookie设置需要通过HttpResponse响应对象调用set_cookie方法来给cookie添加数据。
response = HttpResponse(...) # 或者 response = render(request,...)
# 设置不加盐cookie内容
response.set_cookie(key,value)
# 设置加盐cookie内容
response.set_signed_cookie(key,value,salt="加盐值",max_age=None,...)
参数说明:
- key, 键
- value=’’, 值
- max_age=None, 超时时间
- expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
- path=’/’, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
- domain=None, Cookie生效的域名
- secure=False, https传输
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
删除cookie方法
删除cookie是通过HttpResponse对象调用delete_cookie方法删除
def logout(request):
response = redirect("/login/") # 获取响应对象
response.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值
return repsonse
4.cookie带装饰器的登录验证
url.py文件
from cookie_lesson import views
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^login/', views.login,name="login"),
url(r'^home/', views.home,name="home"),
url(r'^logout/', views.logout,name="logout"),
]
home.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎来到主页</h1>
<a href="{% url 'logout' %}">
注销
</a>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script></script>
</body>
</html>
login.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录界面</h1>
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit">
</form>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script></script>
</body>
</html>
views.py文件
def auth(func):
"""定义装饰器,来对每个操作验证是否登录"""
def wrapper(request):
if request.method == "GET":
# 获取cookie中的数据,如果有而且与服务端发送的cookie数据一致,就通过验证,无需重新登录,直接访问
is_login = request.COOKIES.get('is_login')
username = request.COOKIES.get('user')
if is_login == 'True' and username == "alex":
ret = func(request)
return ret
return redirect('login') # 如果发现cookie中没有,则跳转登录页面
return wrapper
def login(request):
"""登录函数"""
if request.method=="GET":
return render(request,'login.html')
else:
username = request.POST.get("username")
password = request.POST.get("password")
if username == "alex" and password == "alex": # 验证账号密码
ret = render(request,'home.html')
# 登录成功后,添加cookie数据返回客户端
ret.set_cookie("is_login",True)
ret.set_cookie("user",username)
return ret
else:
return redirect('login')
@auth
def home(request):
"""展示主页,登录状态能访问"""
return render(request, "home.html")
@auth
def logout(request):
"""注销,登录状态能访问"""
res = redirect('login')
res.delete_cookie('is_login')
res.delete_cookie('user')
return res
三、session
1.session的由来
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取。
因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
由于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”,我们仍需要cookie来起到桥接作用。
工作原理
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
cookie和session区别
Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;
通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
2.Django中的session
在Django框架中,DJango框架已将cookie和session的数据封装好,存储在自己的数据库中的Django_session表中。
session的相关方法
django将session的数据封装在request请求体中,注意session_id是Django帮我们自动生成的随机字符串。
获取session方法
request.session['key']
request.session.get('key',None)
从cookie里面将sessionid的值取出来,将django-session表里面的对应sessionid的值的那条记录中的session-data字段的数据给你拿出来(并解密)
设置session值
request.session['key'] = 123 # 给session添加键值对数据
request.session.setdefault('key',123) # 如果键存在,不设置,不存在设置键值对
django-session这个表,你不能通过orm来直接控制,因为你的models.py里面没有这个对应关系
删除session值
- del request.session[‘key’] :django-session表里面同步删除
- request.session.delete():仅删除当前会话的所有django_session表中的数据,不常用
- request.session.flush():删除当前的会话django_session表中数据并删除会话的Cookie,常用
session的其他方法
- request.session.session_key :获取session_id的值
- request.session.keys():获取数据库中session的所有键
- request.session.values():获取数据库中session所有的值
- request.session.items():获取数据库中session数据的所有键值对
- request.session.clear_expired():删除数据库中过期的session数据
- request.session.set_expiry(value):设置会话的超时时间。
- value:整数,表示多少秒失效;datetime表示到该时间失效;0表示关闭浏览器就失效;None,依赖全局session失效。
3.Session版登陆验证实例
这里只展示views.py文件的代码,其他与模板文件和url配置与前面cookie版相同。
思路:我们平时访问页面,必须先登录,正常登陆会跳转到home页面。但是有一种情况,有人会直接访问index页面,比如淘宝的购物车,没有登录时去结算,会跳转到登录页面。
如果没有登录直接访问index页面,我们让他直接跳转到登录页面,这个时候如果用户正常登陆成功,我们应该是直接跳转到他之间想要访问的index页面,对不对。
就好比你淘宝去登录成功了,就是直接跳到了结算页面,并不需要你再去点结算页面。
这样用户体验才会比较舒服。
views.py文件
Views.py
4.Django中的session相关设置
-
数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
-
缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
-
文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
-
缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
-
加密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,默认修改之后才保存(默认)
5.CBV模式添加装饰器
CBV添加装饰器的方式:视图函数中已经学习过。
- 加在CBV视图的某个方法上,如get或post方法上。
- 加在dispatch方法上,为所有函数加上装饰器,注意执行父类的dispatch方法,并返回。
- 加在视图类上,但method_decorator必须传 name 关键字参数。
需要导入一个装饰器方法
from django.utils.decorators import method_decorator
项目实践
一个完整的url路由分发,装饰器验证登录的通过CBV模式实现的登录验证项目
项目下总路由分发文件urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^CBV_decoration', include("CBV_decoration.urls")), # include路径分发到应用CBV_decoration下的urls
]
应用CBV_decoration下的urls文件
from django.conf.urls import url
from CBV_decoration.views import Logout,Login,Home,Index
urlpatterns = [
url(r'/login/', Login.as_view(),name="login"), # 登录页面url
url(r'/home/', Home.as_view(),name="home"), # home页面url
url(r'/index/', Index.as_view(),name="index"), # index页面url
url(r'/logout/', Logout.as_view(),name="logout"), # 注销url
]
应用CBV_decoration下的views.py
from django.shortcuts import render,redirect,HttpResponse
from django.views import View
from django.utils.decorators import method_decorator
from functools import wraps
# Create your views here.
def auth(func):
"""定义装饰器,来对每个操作验证是否登录"""
def wrapper(request,*args,**kwargs):
if request.method == "GET":
next_url = request.get_full_path()
if request.session.get('user'): # 如果拿到了用户,说明已经登录,直接允许访问想要访问的页面
ret = func(request,*args,**kwargs)
return ret
else: # 如果没有登录,存下想要访问的url,让他先去登录
return redirect('CBV_decoration/login/?next={}'.format(next_url)) # 如果发现cookie中没有取到用户,则跳转登录页面,并且将想要访问的路径封装在get请求的url中
return wrapper
class Login(View):
"""登录函数"""
def get(self,request):
return render(request,'login.html')
def post(self,request):
username = request.POST.get("username")
password = request.POST.get("password")
if username == "alex" and password == "alex": # 验证账号密码
request.session['user'] = username
# 1.生成随机字符串session_id:随机字符串
# 2.加密用户信息,保存到数据库,Django中保存在Django_session表中
# 3.将session_id:随机字符串放到cookie发送客户端
next_url = request.GET.get('next') # 获取未登录是访问页面的路径
if next_url:
# 如果有直接跳转
return redirect(next_url)
else:
# 没有的话正常登录到home页面
return redirect('home')
class Home(View):
"""展示主页,登录状态能访问"""
@method_decorator(auth)
def dispatch(self, request, *args, **kwargs):
ret = super().dispatch(request, *args, **kwargs)
return ret
def get(self,request):
return render(request, "home.html")
class Index(View):
@method_decorator(auth)
def dispatch(self, request, *args, **kwargs):
ret = super().dispatch(request, *args, **kwargs)
return ret
def get(self,request):
return render(request, "index.html")
class Logout(View):
"""注销,登录状态能访问"""
@method_decorator(auth)
def dispatch(self, request, *args, **kwargs):
ret = super().dispatch(request, *args, **kwargs)
return ret
def get(self,request):
request.session.flush() # 删除当前的会话数据并删除会话的Cookie
# request.session.delete() # 仅仅删除当前会话的所有Session数据
return redirect('login')
模板template文件夹
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录界面</h1>
<form action="" method="post"> <!--注意action路径不填,提交到当前路径-->
{% csrf_token %}
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit">
</form>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script></script>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Home页面</h1>
<a href="{% url 'index' %}">进入首页</a>
<br>
<a href="{% url 'logout' %}">
注销
</a>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script></script>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index页面</h1>
<br>
<a href="{% url 'logout' %}">
注销
</a>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script></script>
</body>
</html>
四、jQuery操作cookie
通过jQuery获取cookie的数据,添加或者删除cookie键值对。
使用之jQuery操作cookie需要引入jQuery.cookie.js插件,而jQuery.cookie.js基于jQuery;
jQuery.cookie.js下载路径:http://plugins.jquery.com/cookie/
<script src="{% static 'jquery/3.4.1/jquery.js' %}"></script> <!--导入jQuery-->
<script src="{% static 'jquery.cookie.js' %}"></script> <!--引用jquery操作cookie的插件-->
1.添加一个cookie会话键值对
$.cookie('key','value')
这里没有指明 cookie有效时间,所创建的cookie有效期默认到用户关闭浏览器为止,所以被称为 “会话cookie(session cookie)”。
2.创建有效期的cookie
$.cookie('key', 'value', { expires: 7 });
这里指明了cookie有效时间,所创建的cookie被称为“持久 cookie (persistent cookie)”。注意单位是:天;
3.创建cookie并设置cookie的有效路径
$.cookie('key', 'value', { expires: 7, path: '/' });
在默认情况下,只有设置 cookie的网页才能读取该 cookie。
如果想让一个页面读取另一个页面设置的cookie,必须设置cookie的路径。cookie的路径用于设置能够读取 cookie的顶级目录。
将这个路径设置为网站的根目录,可以让所有网页都能互相读取 cookie (一般不要这样设置,防止出现冲突)。
4.读取cookie
$.cookie('key');
5.删除cookie
$.cookie('the_cookie', null); //通过传递null作为cookie的值即可
6.可选参数
- expires:(Number|Date)有效期;设置一个整数时,单位是天;也可以设置一个日期对象作为Cookie的过期日期;
- path:(String)创建该Cookie的页面路径;
- domain:(String)创建该Cookie的页面域名;
- secure:(Booblean)如果设为true,那么此Cookie的传输会要求一个安全协议,例如:HTTPS;
$.cookie('key','value',{
expires:7,
path:'/',
domain:'jquery.com',
secure:true
})