jwt = Json Web Token
jwt分三段式:头.体.签名 (head.payload.sgin)
头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
注:登录接口需要做 认证 + 权限 两个局部禁用
第三方写好的 django-rest-framework-jwt
pip install djangorestframework-jwt
新建一个项目,继承AbstractUser表()
class User(AbstractUser):
phone = models.CharField(max_length=11)
avatar = models.ImageField(upload_to='avatar') # ImageField依赖于pillow模块
补充:
# 配置头像相关
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'meida')
# 重写User表相关
AUTH_USER_MODEL = 'api.user'
rest_framework_jwt.views源码
from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken
基类, JSONWebTokenAPIView 继承了APIView
ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken都继承JSONWebTokenAPIView
rest_framework_jwt.views源码:
obtain_jwt_token = ObtainJSONWebToken.as_view()
refresh_jwt_token = RefreshJSONWebToken.as_view()
verify_jwt_token = VerifyJSONWebToken.as_view()
url配置
urlpatterns = [
path('login/', ObtainJSONWebToken.as_view()), # 方式1
path('login/', obtain_jwt_token) # 方式2
]
views.py
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class BookView(APIView):
# 必须加上这个权限认证,否则用户不登录就能访问,那么JWT认证就没意义了
permission_classes = [IsAuthenticated]
authentication_classes = [JSONWebTokenAuthentication]
def get(self, request):
return Response("ok")
自定制Token认证
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework import exceptions
class MyToken(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value = str(request.META.get('HTTP_AUTHORIZATION'))
# 认证
try:
payload = jwt_decode_handler(jwt_value)
except Exception:
raise exceptions.AuthenticationFailed("认证失败")
user = self.authenticate_credentials(payload)
return user, None
views.py
class BookView(APIView):
authentication_classes = [MyToken]
def get(self, request):
return Response("ok")
base64是python内置的模块,也是JWT认证中所用编码和解码方式,需要注意的是,他不算是加密方式,但是对于小白来说也算是加密了,与md5不同的是,md5是固定长度,不可反解,base64是可变长,可反解的。
import base64
import json
# 编码,处理json格式字符串
dic = {"name":"xxx", "age":18}
dic_str = json.dumps(dic)
ret = base64.b64encode(dic_str.encode('utf8')) # 只能编码二进制格式数据
# 解码
dic1 = base64.b64decode(ret)
-第一种方案,自己写登录接口
-第二种写法,用内置,控制登录接口返回的数据格式
-jwt的配置信息中有这个属性
'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',
-重写jwt_response_payload_handler,配置成咱们自己的
源码:
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token
}
重写:
def re_jwt_response_payload_handler(token, user=None, request=None):
return {
'status': 100,
'msg': '登录成功',
'username': user.username,
'token': token,
}
settings.py中添加配置:
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'auth.re_jwt_response_payload_handler', # 自己写的路径
}
from rest_framework.authentication import BaseAuthentication # 基于它
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 基于它
from rest_framework.exceptions import AuthenticationFailed
# from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework_jwt.utils import jwt_decode_handler # 跟上面是一个
import jwt
from api import models
class MyJwtAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value=request.META.get('HTTP_AUTHORIZATION')
if jwt_value:
try:
#jwt提供了通过三段token,取出payload的方法,并且有校验功能
payload=jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.InvalidTokenError:
raise AuthenticationFailed('用户非法')
except Exception as e:
# 所有异常都会走到这
raise AuthenticationFailed(str(e))
# 第一种,去数据库查
# user=models.User.objects.get(pk=payload.get('user_id'))
# 第二种不查库,不过生成的对象只有id和username
# user=models.User(id=payload.get('user_id'),username=payload.get('username'))
# 第三种内置方法,原理就是去数据库中查数据
user=self.authenticate_credentials(payload)
return user,jwt_value
# 没有值,直接抛异常
raise AuthenticationFailed('您没有携带认证信息')
# 使用全局局部配置都可
前端数据格式
{
"username":"yxh/1332323223/[email protected]",
"password":"yxh123"
}
views.py
class LoginView(ViewSet):
def login(self, request, *args, **kwargs):
login_ser = ser.LoginModelSerializer(data=request.data, context={'request': request})
login_ser.is_valid(raise_exception=True)
token = login_ser.context.get('token')
username = login_ser.context.get('username')
return Response({'status': 100, 'msg': '登录成功', 'username': username, 'token': token})
ser.py
class LoginModelSerializer(serializers.ModelSerializer):
# 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过
username = serializers.CharField()
class Meta:
model = models.User
fields = ['username', 'password']
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
# 通过username的数据不同判断,查询字段
if re.match("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$", username):
user = models.User.objects.filter(phone=username).first()
elif re.match('^.+@.+$', username): # 邮箱
user = models.User.objects.filter(email=username).first()
else:
user = models.User.objects.filter(username=username).first()
# 校验用户是否存在
if not user:
raise ValidationError('用户不存在')
if not user.check_password(password):
raise ValidationError('密码错误')
payload = jwt_payload_handler(user) # 把user传入,得到payload
token = jwt_encode_handler(payload) # 把payload传入,得到token
self.context['token'] = token
self.context['username'] = user.username
return attrs
views.py
class LoginView2(ViewSet):
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
# print(username, password)
# 通过username的数据不同判断,查询字段
if re.match("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$", username):
user = models.User.objects.filter(phone=username).first()
elif re.match('^.+@.+$', username): # 邮箱
user = models.User.objects.filter(email=username).first()
else:
user = models.User.objects.filter(username=username).first()
# 校验用户是否存在
if not user:
raise ValidationError('用户不存在')
if not user.check_password(password):
raise ValidationError('密码错误')
payload = jwt_payload_handler(user) # 把user传入,得到payload
token = jwt_encode_handler(payload) # 把payload传入,得到token
username = user.username
login_ser = ser.LoginModelSerializer2(data=request.data)
login_ser.is_valid(raise_exception=True)
return Response({'status': 100, 'msg': '登录成功', 'username': username, 'token': token})
ser.py
class LoginModelSerializer2(serializers.ModelSerializer):
# 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过
username = serializers.CharField()
class Meta:
model = models.User
fields = ['username', 'password']
# jwt的配置
import datetime
JWT_AUTH={
'JWT_RESPONSE_PAYLOAD_HANDLER':'app02.utils.my_jwt_response_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间,手动配置
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # token验证刷新时间,默认就是7天
}