http设计之初,登录状态保持, 就是无状态的,这段时间业务逻辑也非常简单,随着互联网时代的来临,用户量的增加,每次登录却无法状态保持,先出现了cookie,但是cookie存储在客户端的浏览器上,并不安全,于是出现了cookie+session的登录保持状态。
服务端登录的时候,给分配一个session用于存储数据,同时将sessionID返回给浏览器,浏览器通过cookie把sessionID存储起来,下次访问时携带上,服务端就可以通过sessionID来确定用户是否登录
但是,如果服务器宕机了该如何,于是后面都会有多台新服务器备用,但是新服务器没有存储sessionID,于是出现了session共享来解决这个问题,但是缺点依旧很明显,需要服务端存储,用户量多的情况下就需要占大量内存,所以成本太高,于是出现了token技术.
我们既不想存储session,又想验证合法用户,于是出现token技术,我们在登录成功的时候,给用户生成一个token令牌,每次登录的时候,用户只需要第一次登录成功以后,下次登录只需要带上token令牌就可以了,token令牌是经过特殊的算法严格加密的,所以安全性也很高,现在普遍都会使用token来做登录状态的保持。
传统的token做法是用表来对用户认证并保存token:
如图:
这样每当用户登录后,后端生成token并保存到数据库中,设定过期时间,返回token给前端。然后前端每次请求数据,后端都需要获取token,从数据库中读取数据查询 token 是否有效,如果存在判断是否过期。所以每当有请求过来后,这一步操作都会产生一定的性能消耗(查询等待的时间、数据库查询的时间、数据库查询的性能消耗),用户访问量不多的情况下还好,但是当访问量多了,用户数据大了,这一步操作就有可能极大的影响整体的性能。
jwt的做法其实和传统的方式比较类似,但是它不会把token的值存进数据库中,这就是它厉害的地方。它是通过算法来进行用户校验的,如下图:
首先前端一样是把登录信息发送给后端,后端查询数据库校验用户的账号和密码是否正确,正确的话则使用jwt生成token,并且返回给前端。以后前端每次请求时,都需要携带token,后端获取token后,使用jwt进行验证用户的token是否无效或过期,验证成功后才去做相应的逻辑。
JWT 的全称叫做 json web token,它是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature).
下例为JWT的token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJ1c2VybmFtZSI6Ilx1NWMwZlx1N2QyYiIsImV4cCI6MTYzMjQ3NjA1NiwiZW1haWwiOiIifQ.6XnyEvOhL4LFLUNDEhiBRyaXSe6qMD5qQywBauNt-2s
具体分了三段如图:
第一段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 (秘钥加盐)
)
)
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测试:
再对登录进行测试:
最后测试一下权限认证:注意这里测试时需要验证权限必须加上token才能认证成功,具体是在
Headers里加上Authorization=JWT + token(你自己生成的token)