DRF之JWT介绍与使用
JWT介绍和原理
JWT即Json、Web、Token,只针对于web方向的token方式验证。在我们传统登录认证中,都是使用session来认证,而JWT是使用token来认证。
使用session认证时,session都是保存在服务器中,在进行认证过程中,都会去数据库中进行校验(IO操作),效率低。
而token,它保存在客户端中,与cookie一样,也是服务器生成后返回给客户端的,token的格式是三个字符串拼到一起的:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一段我们称它为头部(header);第二段我们称其为载荷(payload,一般都是用户id、用户名、token生效时间等);第三段是签证(signature),由第一段和第二段通过某种加密方式+秘钥加密得到的。
在认证过程中,服务器会先获取第一段和第二段的字符串,通过某种加密方式+秘钥加密得到的新字符串和第三段进行比较,如果一样,那就是认证成功。
token的第一段和第二段字符串其实是通过base64编码得到的,并不是加密后的字符串。
base64解码与解码:
import json
import base64
d = {'typ': 'JWT', 'alg': 'HS256'}
s = json.dumps(d)
# 把字符串进行b64编码
res = base64.b64encode(bytes(s, encoding='utf-8'))
print(res)
# 把b64编码的字符串,解码
b = b'eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9'
res = base64.b64decode(b)
print(res)
使用JWT
JWT签发(生成token)
安装模块:
pip install djangorestframework-jwt
创建auth的user表用户。
createsuperuser
路由配置:
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
向login/路由发送post请求,进行登录。
修改登录成功返回格式
登录成功后只会返回token数据,这样子可能不太好看,所以我们可以自定义返回提示信息。
首先写一个函数:
def my_jwt_response(token, user=None, request=None):
return {'code': 100, 'msg': '登陆成功', 'token': token, 'username': user.username}
配置文件中添加:
JWT_AUTH = {
# 过期时间7天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.my_jwt_response', # 函数路径
}
再次登录:
JWT认证
视图类:
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BookView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# jwt认证类
authentication_classes = [JSONWebTokenAuthentication, ]
# jwt认证类有个小bug,需要配合IsAuthenticated类
permission_classes = [IsAuthenticated, ]
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
访问books/路由,需要在请求头中添加token。
- 键:authorization
- 值:jwt + 空格 + token字符串
JWT认证不通过时有各种提示信息:比如
token过期
解码错误
自定义JWT
签发
用户表:
class User(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=16)
登录视图:
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user_obj = User.objects.filter(username=username, password=password).first()
if user_obj:
"""登录成功,进行签发"""
# 根据登录用户获取载荷
payload = jwt_payload_handler(user_obj)
print(payload)
# 根据载荷获取token
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'token': token})
return Response({'code': 99, 'msg': '用户名或密码错误'})
路由配置:
urlpatterns = [
path('login/', views.LoginView.as_view()),
]
认证
编写认证类:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 从请求头获取token
jwt_value = request.META.get('HTTP_TOKEN')
try:
# 根据token获取载荷
payload = jwt_decode_handler(jwt_value)
except Exception:
raise AuthenticationFailed('你还未登录')
# 根据载荷中存的用户id获取用户对象
user = User.objects.filter(pk=payload['user_id'])
return user, jwt_value
视图类:
class BookView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 添加自己编辑的认证类
authentication_classes = [LoginAuth, ]
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
token错误、过期,或未携带,都提示未登录
携带正确的token