登录状态保持(cookie+session和token)

http设计之初,登录状态保持, 就是无状态的,这段时间业务逻辑也非常简单,随着互联网时代的来临,用户量的增加,每次登录却无法状态保持,先出现了cookie,但是cookie存储在客户端的浏览器上,并不安全,于是出现了cookie+session的登录保持状态。

1.cookie+session机制

服务端登录的时候,给分配一个session用于存储数据,同时将sessionID返回给浏览器,浏览器通过cookie把sessionID存储起来,下次访问时携带上,服务端就可以通过sessionID来确定用户是否登录

但是,如果服务器宕机了该如何,于是后面都会有多台新服务器备用,但是新服务器没有存储sessionID,于是出现了session共享来解决这个问题,但是缺点依旧很明显,需要服务端存储,用户量多的情况下就需要占大量内存,所以成本太高,于是出现了token技术.

2.token技术

我们既不想存储session,又想验证合法用户,于是出现token技术,我们在登录成功的时候,给用户生成一个token令牌,每次登录的时候,用户只需要第一次登录成功以后,下次登录只需要带上token令牌就可以了,token令牌是经过特殊的算法严格加密的,所以安全性也很高,现在普遍都会使用token来做登录状态的保持。

传统的token做法是用表来对用户认证并保存token:

如图:

登录状态保持(cookie+session和token)_第1张图片

登录状态保持(cookie+session和token)_第2张图片

 这样每当用户登录后,后端生成token并保存到数据库中,设定过期时间,返回token给前端。然后前端每次请求数据,后端都需要获取token,从数据库中读取数据查询 token 是否有效,如果存在判断是否过期。所以每当有请求过来后,这一步操作都会产生一定的性能消耗(查询等待的时间、数据库查询的时间、数据库查询的性能消耗),用户访问量不多的情况下还好,但是当访问量多了,用户数据大了,这一步操作就有可能极大的影响整体的性能。

3.JWT技术

jwt的做法其实和传统的方式比较类似,但是它不会把token的值存进数据库中,这就是它厉害的地方。它是通过算法来进行用户校验的,如下图:

登录状态保持(cookie+session和token)_第3张图片

首先前端一样是把登录信息发送给后端,后端查询数据库校验用户的账号和密码是否正确,正确的话则使用jwt生成token,并且返回给前端。以后前端每次请求时,都需要携带token,后端获取token后,使用jwt进行验证用户的token是否无效或过期,验证成功后才去做相应的逻辑。

4.什么是JWT

JWT 的全称叫做 json web token,它是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature).

下例为JWT的token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJ1c2VybmFtZSI6Ilx1NWMwZlx1N2QyYiIsImV4cCI6MTYzMjQ3NjA1NiwiZW1haWwiOiIifQ.6XnyEvOhL4LFLUNDEhiBRyaXSe6qMD5qQywBauNt-2s

具体分了三段如图: 

 登录状态保持(cookie+session和token)_第4张图片

第一段HEADER部分,其内容固定包含算法和token类型,这里看到加密方式是使用HS256进行加密,token类型是JWT,然后对json进行base64url加密,这就是token的第一段。

{
  "alg": "HS256",
  "typ": "JWT"
}

第二段PAYLOAD部分,包含一些数据(一般是用户希望存储的数据,其中还会带有一个超时时间),然后对此json进行base64url加密,这就是token的第二段。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

第三段SIGNATURE部分,把前两段加密后的base64url密文通过.拼接起来,然后对其进行HS256加密,然后对HS256密文再进行base64url加密,最终得到token的第三段。

base64url(
    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      your-256-bit-secret (秘钥加盐)
    )
)

优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

  安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

5. JWT具体的代码实现

5.1安装依赖以及所需要的包

 在终端输入:

pip install djangorestframework-jwt

settings里的配置:这里全局配置JWT验证设置,如果测试的时候可以先把全局验证关掉,或者在视图里导入 AllowAny(from rest_framework.permissions import AllowAny)方法,然后在不需要jwt验证的类里加入permission_classes = [AllowAny]容许通过验证即可。

########### 1、在INSTALLED_APPS中加入'rest_framework.authtoken', #################
INSTALLED_APPS = [
    '''
    'rest_framework.authtoken',  # 
    '''
]

################### 2、配置jwt验证 ######################
REST_FRAMEWORK = {
    # 身份认证
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
#全局配置JWT验证设置
'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        ),
}

import datetime

JWT_AUTH = {
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_RESPONSE_PAYLOAD_HANDLER':
        'users.views.jwt_response_payload_handler',  # 重新login登录返回函数
}
AUTH_USER_MODEL='users.User'  # 指定使用users APP中的 model 

models.py这里重写user表继承AbstractUser类,代码如下:

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.

class Users(AbstractUser):
    nickname = models.CharField(max_length=64)
    age = models.IntegerField(default=18)
    home = models.CharField(max_length=64)
    username = models.CharField(max_length=64, unique=True)
    password = models.CharField(max_length=255)
    phone = models.CharField(max_length=64)
    token = models.CharField(max_length=255)

然后在执行如下代码,迁移生成表:

python manage.py makemigrations
python manage.py migrate

如果报错先将settings里的配置注释掉,然后先删掉迁移文件然后删除表后重新迁移

序列化器serializers.py文件:

from .models import Users
from rest_framework_jwt.settings import api_settings
from rest_framework import serializers


class UserSerializer(serializers.Serializer):
    nickname = serializers.CharField(max_length=64)
    age = serializers.IntegerField(default=18)
    home = serializers.CharField(max_length=64)
    username = serializers.CharField()
    password = serializers.CharField()
    phone = serializers.CharField()
    token = serializers.CharField(read_only=True)

    def create(self,data):
        user = Users.objects.create(**data)
        user.set_password(data.get('password'))
        user.save()
        # 补充生成记录登录状态的token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        user.token = token
        return user

视图views.py,这里完成了注册(在序列化器中完成了入库操作,入库时对密码进行了加密操作这里调用了set_password的方法,底层实现时调用了make_password的方法,以及生成token也是在序列化器中进行的如上图),登录,以及权限验证:

from rest_framework.views import APIView
from rest_framework.views import Response
from rest_framework.permissions import IsAuthenticated,AllowAny
from jwtapp.ser import UserSerializer
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

#用户注册
class RegisterView(APIView):
    permission_classes = [AllowAny]
    def post(self,request):
        ser = UserSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data,status=201)
        return Response(ser.errors,status=400)

#用户登录
def jwt_response_payload_handler(token,user=None,request=None):
    '''
    :param token:
    :param user:
    :param request:
    :return:
    '''
    return {
        "token":token,
        "user":user.username,
        "userid":user.id
    }

#测试必须携带token才能访问接口
class UserList(APIView):
    permission_classes = [IsAuthenticated]
    authentication_classes = [JSONWebTokenAuthentication]
    def get(self,request):
        print(request.META.get('HTTP_AUTHORIZATION',None))
        return Response({"msg":"认证成功"})
    def post(self,request):
        return Response({"msg": "认证成功"})

最后是urls的配置:

from django.contrib import admin
from django.urls import path
from jwtapp.views import *
from django import views
# 验证密码后返回token
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [

    path('reg/',RegisterView.as_view()),
    path('login/', obtain_jwt_token), #用户登陆后返回token
    path('list/', UserList.as_view()), #测试时需要携带token才能访问

]

代码写完以后我们进行测试,可以自己写测试脚本,也可以使用postman进行测试:

先对注册进行测试如图为postman测试:

登录状态保持(cookie+session和token)_第5张图片

 再对登录进行测试:

登录状态保持(cookie+session和token)_第6张图片

 最后测试一下权限认证:注意这里测试时需要验证权限必须加上token才能认证成功,具体是在

Headers里加上Authorization=JWT + token(你自己生成的token)

登录状态保持(cookie+session和token)_第7张图片

你可能感兴趣的:(http,html)