在上一章中已经完成了注册的接口了,现在需要完成登录
因为登录采用了JWT方式进行校验,所以需要继承rest_framework_simplejwt.views
中的视图
登录
代码如下
from rest_framework_simplejwt.views import TokenObtainPairView
class LoginView(TokenObtainPairView):
"""
登录视图
"""
serializer_class = MyTokenObtainPairSerializer
测试
使用postman进行测试
请求地址:http://127.0.0.1:8000/users/login/
请求方式:POST
请求参数:
{
"username": "zhongxin",
"password": "123456"
}
请求结果
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2MDk2NDg1OCwiaWF0IjoxNjYwODc4NDU4LCJqdGkiOiI0Njg3MzAyZGE3ZjM0NzlkODE0NmUxNzU4ZTA0M2E0ZCIsInVzZXJfaWQiOjF9.V2R9h1NDXu33vmiM5rcGOv5xpODK-37sD-_GcWmxn8Q",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjYwOTY0ODU4LCJpYXQiOjE2NjA4Nzg0NTgsImp0aSI6IjZjYjZhYzc0NDA4ZjRiYzdhMzlmM2Q0ODBhNGQyYmUyIiwidXNlcl9pZCI6MX0.dg2mKJXq1pWV-80RIWzriaFmnf2AgsaTpl0bgkVpb3g",
"userInfo": {
"userId": 1,
"userName": "测试游记",
"dashboard": "0",
"is_superuser": false,
"role": []
}
}
分析
其中serializer_class
指定为之前写的序列化器
继承的TokenObtainPairView
需要分析一下
TokenViewBase
查看一下它的代码
Takes a set of user credentials and returns an access and refresh JSON web
token pair to prove the authentication of those credentials.获取一组用户凭据并返回访问和刷新json web令牌对,以证明这些凭据的身份验证。
class TokenObtainPairView(TokenViewBase):
"""
Takes a set of user credentials and returns an access and refresh JSON web
token pair to prove the authentication of those credentials.
"""
_serializer_class = api_settings.TOKEN_OBTAIN_SERIALIZER
它也有一个_serializer_class
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
查看它的序列化器rest_framework_simplejwt.serializers.TokenObtainPairSerializer
class TokenObtainPairSerializer(TokenObtainSerializer):
token_class = RefreshToken
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data["refresh"] = str(refresh)
data["access"] = str(refresh.access_token)
if api_settings.UPDATE_LAST_LOGIN:
update_last_login(None, self.user)
return data
它的继承关系图
这里比较复杂,所以只先关注get_token
和update_last_login
get_token
这个方法来自父类
@classmethod
def get_token(cls, user):
return cls.token_class.for_user(user)
其中token_class
在父类是None
,在子类为RefreshToken
Token中的for_user
@classmethod
def for_user(cls, user):
"""
Returns an authorization token for the given user that will be provided
after authenticating the user's credentials.
"""
user_id = getattr(user, api_settings.USER_ID_FIELD)
if not isinstance(user_id, int):
user_id = str(user_id)
token = cls()
token[api_settings.USER_ID_CLAIM] = user_id
return token
在api_settings
中
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
没有特殊修改的话就是
token["user_id"] = user.id
另外的字段在token = cls()
中生成
rest_framework_simplejwt.tokens.Token.__init__
里面不细看了,总之会返回这样一个token
update_last_login
如果配置了UPDATE_LAST_LOGIN
则会触发update_last_login
操作
def update_last_login(sender, user, **kwargs):
"""
A signal receiver which updates the last_login date for
the user logging in.
"""
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
它就是获取了当前时间,并把该时间记录为最后登录的时间
要让它生效的话,修改下backend/LightSeeking/settings.py
中的SIMPLE_JWT
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1), # token过期时间1天
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
"UPDATE_LAST_LOGIN": True, # 记录最后登录时间
}
发起请求后查看数据库的last_login
字段,发现时间变为了当前时间(时区为0)
异常处理
之前在backend/LightSeeking/settings.py
的REST_FRAMEWORK
写了
# DRF的配置
REST_FRAMEWORK = {
...
# 异常处理
'EXCEPTION_HANDLER': 'utils.exception.exception_handler'
}
说明我们需要自定义DRF的异常处理方法
原来的异常处理方法可以见DRF的settings.py:venv/lib/python3.9/site-packages/rest_framework/settings.py
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None
我们仿照一下在backend/utils/exception.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
def exception_handler(exc, context):
"""
自定义处理异常函数
:param exc:
:param context:
:return:
"""
response = drf_exception_handler(exc, context)
if response is None: # 处理之后为空,再进行自定义的二次处理
# print(exc) # 错误原因 还可以做更详细的原因,通过判断exc信息类型
# print(context) # 错误信息
print('%s - %s - %s' % (context['view'], context['request'].method, exc))
return Response({
'detail': '服务器错误'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
return response # 处理之后有值,就直接返回结果
这样我们就可以对异常进行自定义的处理了