一、安装djangorestframework-jwt
拓展
pip install djangorestframework-jwt
编辑dev.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
# 追加Token认证后端 —— 用于验证token有效期识别用户身份
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
# 有效期设置为10天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=10),
}
二、手动定义序列化器和视图实现
我们先尝试着自己写视图和序列化器来完成传统身份认证登陆接口;
此处我们需要体会:在DRF编程中,我们需要尽可能使用序列化器来完成主要业务代码,视图仅仅用做映射路由,调用序列器来完成主要业务!简单一句话就是 —— 把视图的业务代码剥离开,统一组织到序列化器中实现!
1、新建apps/meiduo_admin/serializers/loginserializers.py
from django.contrib.auth import authenticate
from rest_framework import serializers
from rest_framework_jwt.utils import jwt_payload_handler,jwt_encode_handler
# 明确,定义一个序列化器对username和password这两个字段进行校验
# 校验的前端传参是:{"username": 'weiwei', 'password': 'xxxxxx'}
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(
required=True,
max_length=20,
allow_blank=False,
allow_null=False
)
password = serializers.CharField(
required=True,
max_length=20,
min_length=8,
allow_blank=False,
allow_null=False
)
# 登陆校验用户名和密码 —— 自定义校验
def validate(self, attrs):
# attrs = {"username": 'weiwei', 'password': 'xxxxxx'}
# 1、传统身份校验(校验用户和密码)
user = authenticate(**attrs)
if user is None:
raise serializers.ValidationError("用户名或密码错误!")
# 2、传统身份校验成功 —— 签发token(令牌) —— 把该token作为有效数据的一部分
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
# 3、返回有效数据
return {
'user': user,
'token': token
}
2、新建apps/meiduo_admin/views/login_views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from ..serializers.loginserializers import LoginSerializer
class LoginView(APIView):
def post(self, request):
# 1、提取参数
# 2、校验参数
# 3、数据处理
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 4、构建响应
return Response({
'token': serializer.validated_data.get('token'),
'username': serializer.validated_data['user'].username,
'user_id': serializer.validated_data['user'].id
})
3、编辑apps/meiduo_admin/urls.py
from django.urls import re_path
from meiduo_admin.views import login_views
urlpatterns = [
# 登陆路由
re_path(r'^authorizations/$', login_views.LoginView.as_view()),
]
三、使用djangorestframework-jwt
拓展提供的视图接口完成
- 前面我们是自己写视图和序列化器来完成传统身份认证登陆接口,其实Django已经提供有封装好的函数供我们调用,不需要自己写代码实现序列器和视图函数进行身份认证登录。
1、编辑apps/meiduo_admin/urls.py
映射接口视图
导包:from rest_framework_jwt.views import obtain_jwt_token
from django.urls import re_path
from meiduo_admin.views import login_views
# obtain_jwt_token为拓展插件提供的用于验证用户名和密码并签发token的视图
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# 登陆路由
# re_path(r'^authorizations/$', login_views.LoginView.as_view()),
re_path(r'^authorizations/$', obtain_jwt_token),
]
测试登录:
- 我们发现,返回的数据只有
token
,它返回的数据格式是由方法jwt_response_payload_handler
返回的数据决定的,不符合业务需求,前端要求我们返回的数据有:token
、username
、user_id
。
因此,我们需要重写jwt_response_payload_handler
方法
2、新建meiduo_mall/utils/jwt_response_handlers.py
模块,自定义obtain_jwt_token
视图中用于构造响应的函数
"""
重写后台管理系统身份登陆校验方法的响应数据
"""
def jwt_response_payload_handler(token, user=None, request=None):
return {
# 补充返回的username和user_id字段
'username': user.username,
'user_id': user.id,
'token': token
}
3、编辑dev.py
JWT_AUTH = {
# 设置签发的token的有效期
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=100),
# 来指定拓展插件默认视图返回的响应参数构造函数
'JWT_RESPONSE_PAYLOAD_HANDLER': 'meiduo_mall.utils.jwt_response_handlers.jwt_response_payload_handler'
}
- 测试如下:返回的数据格式已经变成了我们业务需求的格式
四、登录接口扩展(权限限定)
1、业务需求:后台管理站点只允许员工/管理员(is_staff=True)
的账号登陆签发token;
2、编辑apps/users/utils.py
# 继承Django默认的传统认证后端
class UsernameMobileAuthBackend(ModelBackend):
# 重写authenticate方法
# 原因:默认的authenticate方法,只会根据username字段去过滤查找用户
def authenticate(self, request, username=None, password=None, **kwargs):
# 允许多账号登陆的情况下,前端传来的"username"有可能是用户名也有可能是手机号
try:
# 1、先按用户名查找
user = User.objects.get(
# username=="18588269037" or mobile=="18588269037"
Q(username=username) | Q(mobile=username) | Q(email=username)
)
except User.DoesNotExist as e:
return None # 用户名找不到,返回None表示认证失败
# TODO:判断是否为管理站点页面登陆,如果是需要进一步校验is_staff=True
# 如果是商城页面登陆,request是一个请求对象
# 如果是管理站点页面登陆,request是一个None
if request is None and not user.is_staff:
return None
# 3、某一个找到了,再校验密码
if user.check_password(password):
return user
- 这里有个问题,为什么:
如果是商城页面登陆,request是一个请求对象?
如果是管理站点页面登陆,request是一个None?
当我们进行登录请求后端时,都会调用重写的authenticate
方法进行校验,该方法默认参数request=None
,而我们在之前开发商城登录接口的时候,手动给参数request
赋了一个request
值:
- 源代码
authenticate
方法
- 之前写的登录接口代码:
所以:
- 如果是商城页面登陆,request是一个请求对象
- 如果是管理站点页面登陆,request是一个None