内容概览
- cookie,session,token介绍
- jwt原理介绍
- base64编码与解码
- drf-jwt快速使用
- drf-jwt修改返回格式
- 自定义user表,签发token
cookie,session,token介绍
- 无cookie
在很早之前,web基本就是文档的浏览,不需要记录用户,每次请求都是一个新的http请求 - cookie+session
随着交互式web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样, 每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了 - cookie+session存在问题
每个人只需要保存自己的session id,而服务器要保存所有人的session id ! 如果访问服务器多了, 就得由成千上万,甚至几十万个。
这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力 - Token认证
token认证分为三段
第一段:头,公司信息,加密方式…
第二段:荷载,真正的数据信息
第三段:签名,通过第一段和第二段,通过某种加密方式加密得到
用户登录后将第一段加第二段在加上自己才知道的盐,通过某种加密方式得到第三段签名,将整个返回给客户端保存
用户再次访问时携带这个组数据,服务端再获取第一段与第二段以原来的方式加密,与第三段签名对比,相同则数据正常
使用token的认证机制后,服务端就不需要保存数据,而是保存在各自的客户端上了
jwt原理介绍
JWT(Json Web Token),token的应用于web方向的就称为jwt
# 构成和工作原理
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# header:头
1. 声明类型,这里是jwt
2. 声明加密的算法 通常直接使用 HMAC SHA256
3. 公司信息
由{'typ': 'JWT','alg': 'HS256'}
变成了(base64的编码):eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# payload:荷载
1. exp: jwt的过期时间,这个过期时间必须要大于签发时间
2. iat: jwt的签发时间
3. 用户信息: 用户信息
由{"exp": "1234567890","name": "John Doe","userid": 3}
变成了(base64的编码):eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
# signature:签名
把头和荷载加密后得到的:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
"""注意:secret是保存在服务器端的(加密方式+盐),jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了"""
# jwt使用流程最核心的是:
签发:登录接口签发
认证:认证类认证
base64编码与解码
base64可以把字符串编码成base64的编码格式
与加密不同,base64可以解码回原来的数据
应用场景:jwt中使用;网络中传输字符串可以使用;网络中传输图片可以使用
# 使用方式
import base64
import json
d1 = {'name':'al', 'age': 17}
d1 = json.dumps(d1)
res = base64.b64encode(d1.encode()) # 只能够编码bytes类型字符串,所以先序列化为json格式字符串,转为bytes格式再编码
print(res) # b'eyJuYW1lIjogImFsIiwgImFnZSI6IDE3fQ=='
bs = 'eyJuYW1lIjogImFsIiwgImFnZSI6IDE3fQ=='
bs = base64.b64decode(bs) # 可以直接解码字符串和bytes类型的数据
print(bs) # b'{"name": "al", "age": 17}'
drf-jwt快速使用
django中使用jwt有三种方式
- 自己写
- 使用djangorestframework-simplejwt(比较新)
- 使用django-rest-framework-jwt(比较老)
1. 安装第三方模块
pip install djangorestframework-jwt
2. 迁移表,因为它默认使用auth的user表签发token
python manage.py makemigrations
python manage.py migrate
3. 创建超级用户(auth的user表中要有记录)
4. 不需要写登录接口,如果使用的auth的user表作为用户表,他可以快速签发
5. 直接配置路由,因为它已经写好了登录接口
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
6. 在视图类上配置认证类,还需要配合权限类使用
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class TestView(APIView):
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self, reqeust):
return Response('ok')
7. 前端访问时,token需要放在请求头中
Authorization:jwt token串
drf-jwt修改返回格式
"""登录成功后,前端看到的格式是固定的,如果想要修改返回的格式,需要写一个函数,配置到配置文件中,函数返回什么前端就看到什么"""
# 1. 先写一个函数
def jwt_response(token, user=None, request=None):
return {'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token}
# 2. 将函数配置到配置文件中
JWT_AUTH={
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.views.jwt_response'
}
"""配置完成后,以后登录接口返回的格式就是我们写的函数的返回值"""
自定义user表,签发token
# 1. 先创建表模型(models.py)
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
# 2. 写一个登录接口(views.py)
from rest_framework.exceptions import APIException
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 = UserInfo.objects.filter(username=username, password=password).first()
if user:
"""使用djagnorestframework-jwt模块提供的签发token的函数,生成token"""
payload = jwt_payload_handler(user) # 通过user对象---》{username:lqz,id:1,过期时间}
token = jwt_encode_handler(payload) # 根据payload---》得到token:头.荷载.签名
return Response({'code':100,'msg':'登录成功','token':token})
raise APIException('用户名或密码错误')
练习
自定义认证类,验证token
"""认证类"""
import jwt
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.utils import jwt_decode_handler
from app01.models import UserInfo
class Jwt_Auth(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_JWT')
if token is None:
return None
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
msg = '签名已经过期'
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '错误解码签名。'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
user = UserInfo.objects.get(pk=payload.get('user_id'))
return (user, token)
"""权限类"""
from rest_framework.permissions import BasePermission
class Jwt_permission(BasePermission):
def has_permission(self, request, view):
return bool(request.user)
"""视图类"""
from app01.models import UserInfo
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework_jwt.settings import api_settings
from app01.authentications import Jwt_Auth
from app01.permissions import Jwt_permission
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class TestView(APIView):
authentication_classes = [Jwt_Auth]
permission_classes = [Jwt_permission]
def get(self, reqeust):
return Response('ok')
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = UserInfo.objects.filter(username=username, password=password).first()
if user:
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'token': token})
raise APIException('用户名或密码错误')