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登录

  • 创建数据模型
  1. class Users(models.Model):
  2. username = models.CharField(max_length=128, unique=True)
  3. password = models.CharField(max_length=128)
  4. age = models.IntegerField(default=18)
  5. gender = models.CharField(max_length=8)
  • 创建login路由和登录成功后的用户详情页面路由
  1. urlpatterns = [
  2. url(r'^login/', views.login, name='login'),
  3. url(r'^mine/', views.mine, name='mine'),
  4. ]
  • 创建login函数,加载登录页面,处理登录请求
  1. def login(request):
  2. # 判断请求方法
  3. if request.method == 'POST':
  4. username = request.POST.get('user_name')
  5. password = request.POST.get('password')
  6. # 根据request中username和password到数据库中查询是否存在
  7. users = Users.objects.filter(Q(username=username) & Q(password=password))
  8. if users.exists():
  9. # 验证通过则跳转到用户详情页,并将cookie写入response返回给客户端
  10. response = HttpResponseRedirect(reverse('apps:mine'))
  11. response.set_cookie('username', username)
  12. return response
  13. # 验证不通过跳转到登录页面
  14. return render(request, 'login.html')
  15. # get请求直接进入登录页面
  16. return render(request, 'login.html')
  • 创建login登录静态页面
  1. <form action="{% url 'apps:login' %}" method="post">
  2. <span>用户名</span><input type="text" placeholder="输入用户名" name="user_name">
  3. <span>密码<span/><input type="password" name="password"></span>
  4. <button>登录</button>
  5. </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>