HTTP无状态协议
HTTP的无状态性表现为两次请求是没有关系的,为了使某个站点下的所有网页能共享数据,就需要记录用户的状态,而实现方式主要有Cookie、Session、Token等。
Cookie
Cookie指的是浏览器中存储的一种数据,处理流程如下:
- 浏览器向服务端发起HTTP请求。
- 服务端生成Cookie并发送给浏览器。
- 浏览器将Cookie以key:value的形式保存到文本文件中,下次请求同一站点时将Cookie发送给服务器。
Cookie存在以下问题:
- 安全性较低,因为保存在客户端,可能会存在篡改。
- Cookie有大小限制,一般大小是4K。
- Cookie有数量限制,根据浏览器不同,一般同一域名下为50个。
Session
Cookie是存储在客户端浏览器中,而Session是保存在服务端的,处理流程如下:
- 浏览器向服务端发起HTTP请求。
- 服务端生成并且保存身份信息相关的Session数据。
- 服务端将对应的sessionid写入cookie,并传输给客户端。
- 客户端将cookie保存在本地,下次请求时会带上Cookie中的sessionid发送至服务端
Django中Session特点:
- 数据存储在服务器中
- Django默认会把Session持久化到数据库中
- Django中Session的默认过期时间是14天
- Session依赖于Cookie
Token
- 服务端会话技术
- 自定义的session
- 如果在Web开发中,使用起来和Session基本一致
- 如果使用在移动端开发中,通常以Json形式传输,需要移动端自己存储Token,需要获取Token的关联数据时,主动传递Token
Django实现Cookie登录
- 创建数据模型
class Users(models.Model):username = models.CharField(max_length=128, unique=True)password = models.CharField(max_length=128)age = models.IntegerField(default=18)gender = models.CharField(max_length=8)
- 创建login路由和登录成功后的用户详情页面路由
urlpatterns = [url(r'^login/', views.login, name='login'),url(r'^mine/', views.mine, name='mine'),]
- 创建login函数,加载登录页面,处理登录请求
def login(request):# 判断请求方法if request.method == 'POST':username = request.POST.get('user_name')password = request.POST.get('password')# 根据request中username和password到数据库中查询是否存在users = Users.objects.filter(Q(username=username) & Q(password=password))if users.exists():# 验证通过则跳转到用户详情页,并将cookie写入response返回给客户端response = HttpResponseRedirect(reverse('apps:mine'))response.set_cookie('username', username)return response# 验证不通过跳转到登录页面return render(request, 'login.html')# get请求直接进入登录页面return render(request, 'login.html')
- 创建login登录静态页面
<form action="{% url 'apps:login' %}" method="post"><span>用户名</span><input type="text" placeholder="输入用户名" name="user_name"><span>密码<span/><input type="password" name="password"></span><button>登录</button></form>
- 创建页面详情函数
def mine(request):
# 获取request中的cookie信息
cook = request.COOKIES.get('username')
if cook:
# 如果存在则返回用户信息
return HttpResponse('{}-cookie success'.format(cook))
# 如不存在则跳转登录页面
return render(request, 'login.html')
此时,执行http://10.211.55.9:8000/apps/login/时,登录后会自动跳转到mine页面,用别的窗口直接访问http://10.211.55.9:8000/apps/mine/也无需登录。通过F12 debug模式可以看到Cookie: username=tom-2信息。
set_cookie方法
- set_cookie
response.set_cookie(key, value, max_age=None, exprise=None)
max_age:整数,指定cookie的过期时间,0表示浏览器关闭失效时间
exprise:整数,指定cookie的过期时间,同时支持一个datetime或timedelta,如timedelta(days=10)表示10天后过期
- signet_cookie:加了签名的cookie,并非是加密的cookie
response.set_signed_cookie(key, value, salt='', **kwargs)
因此上面的示例中login函数的set_cookie可以改写为:
response.set_signed_cookie('username', username, salt='is_login', max_age=10)
获取cookie并判断可以改写为:
cook = request.get_signed_cookie('username', salt='is_login')
Django实现Session保持
- 创建Login_session登录路由
urlpatterns = [
url(r'^login_session/', views.login_session, name='login_session'),
url(r'^mine_session/', views.mine_session, name='mine_session'),
]
- 创建login_session和mine_session视图
def login_session(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
username = request.POST.get("user_name")
# 将session保存到数据库中
request.session['username'] = username
return HttpResponse('登录成功')
def mine_session(request):
username = request.session.get('username')
return HttpResponse(username)
与Cookie不同,Session的设置和获取直接操作request
request.session["is_login"] = 1
# 设置超时时间 (Cookie和Session数据的)
request.session.set_expiry(7)
# 存在则不设置
request.session.setdefault('k1',123)
# 指定删除的键值对
del request.session['k1']
# 只删除session数据
request.session.delete()
# 清除Cookie和Session数据
request.session.flush()
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
- 退出登录,清除session
def logout_session(request):
# 删除session数据和cookie
request.session.flush()
return HttpResponseRedirect(reverse('apps:mine_session'))
Token的实现
- 创建数据模型,存储token信息
class Users(models.Model):
username = models.CharField(max_length=128, unique=True)
password = models.CharField(max_length=128)
age = models.IntegerField(default=18)
gender = models.CharField(max_length=8)
token = models.CharField(max_length=128)
- 创建token登录URL
urlpatterns = [
url(r'^login_token', views.login_token, name='login_token'),
url(r'^mine_token', views.mine_token, name='mine_token'),
]
- 创建login_token和mine_token视图
# 生成Token
def generate_token(ip, username):
return hashlib.new("md5", (ip + time.ctime() + username).encode("utf-8")).hexdigest()
def login_token(request):
if request.method == 'GET':
return render(request, 'login_token.html')
elif request.method == 'POST':
username = request.POST.get('user_name')
password = request.POST.get('password')
users = Users.objects.filter(username=username).filter(password=password)
if users.exists():
user = users.first()
ip = request.META.get('REMOTE_ADDR')
# 生成Token
token = generate_token(ip, username)
user.token = token
# 保存至数据库
user.save()
data = {
'status': 200,
'msg': '登录成功',
'token': token
}
# 将Token发送至客户端
return JsonResponse(data=data)
data = {
'status': 801,
'msg': '登录失败',
}
return JsonResponse(data=data)
def mine_token(request):
# 客户端通过http://10.211.55.9:8000/apps/mine_token?token=2ac31e48bbe96315ad6cc735789ccf5f方式传递token令牌
token = request.GET.get('token')
try:
# 查询token是否存在
user = Users.objects.get(token=token)
except Exception as e:
return redirect(reverse("apps:login_token"))
data = {
"msg": "ok",
"status": 200,
"data": {
"username": user.username
}
}
return JsonResponse(data=data)
- 创建登录页面
<form action="{% url 'apps:login_token' %}" method="post">
<span>用户名</span><input type="text" placeholder="输入用户名" name="user_name">
<span>密码</span><input type="password" name="password">
<button>登录</button>
</form>
