前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >7.寻光集后台管理系统-用户管理(登录视图)

7.寻光集后台管理系统-用户管理(登录视图)

作者头像
zx钟
发布2022-12-02 15:52:16
7710
发布2022-12-02 15:52:16
举报
文章被收录于专栏:测试游记测试游记

在上一章中已经完成了注册的接口了,现在需要完成登录

因为登录采用了JWT方式进行校验,所以需要继承rest_framework_simplejwt.views中的视图

登录

代码如下

代码语言:javascript
复制
from rest_framework_simplejwt.views import TokenObtainPairView

class LoginView(TokenObtainPairView):
    """
    登录视图
    """
    serializer_class = MyTokenObtainPairSerializer

测试

使用postman进行测试

请求地址:http://127.0.0.1:8000/users/login/

请求方式:POST

请求参数:

代码语言:javascript
复制
{
    "username": "zhongxin",
    "password": "123456"
}

请求结果

代码语言:javascript
复制
{
    "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY2MDk2NDg1OCwiaWF0IjoxNjYwODc4NDU4LCJqdGkiOiI0Njg3MzAyZGE3ZjM0NzlkODE0NmUxNzU4ZTA0M2E0ZCIsInVzZXJfaWQiOjF9.V2R9h1NDXu33vmiM5rcGOv5xpODK-37sD-_GcWmxn8Q",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjYwOTY0ODU4LCJpYXQiOjE2NjA4Nzg0NTgsImp0aSI6IjZjYjZhYzc0NDA4ZjRiYzdhMzlmM2Q0ODBhNGQyYmUyIiwidXNlcl9pZCI6MX0.dg2mKJXq1pWV-80RIWzriaFmnf2AgsaTpl0bgkVpb3g",
    "userInfo": {
        "userId": ,
        "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令牌对,以证明这些凭据的身份验证。

代码语言:javascript
复制
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

代码语言:javascript
复制
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",

查看它的序列化器rest_framework_simplejwt.serializers.TokenObtainPairSerializer

代码语言:javascript
复制
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_tokenupdate_last_login

get_token

这个方法来自父类

代码语言:javascript
复制
@classmethod
def get_token(cls, user):
    return cls.token_class.for_user(user)

其中token_class在父类是None,在子类为RefreshToken

Token中的for_user
代码语言:javascript
复制
@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

代码语言:javascript
复制
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",

没有特殊修改的话就是

代码语言:javascript
复制
token["user_id"] = user.id

另外的字段在token = cls()中生成

代码语言:javascript
复制
rest_framework_simplejwt.tokens.Token.__init__

里面不细看了,总之会返回这样一个token

update_last_login

如果配置了UPDATE_LAST_LOGIN则会触发update_last_login操作

代码语言:javascript
复制
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

代码语言:javascript
复制
# JWT配置
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=),  # token过期时间1天
    'REFRESH_TOKEN_LIFETIME': timedelta(days=),
    "UPDATE_LAST_LOGIN": True,  # 记录最后登录时间
}

发起请求后查看数据库的last_login字段,发现时间变为了当前时间(时区为0)

异常处理

之前在backend/LightSeeking/settings.pyREST_FRAMEWORK写了

代码语言:javascript
复制
# DRF的配置
REST_FRAMEWORK = {
    ...
    # 异常处理
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler'
}

说明我们需要自定义DRF的异常处理方法

原来的异常处理方法可以见DRF的settings.py:venv/lib/python3.9/site-packages/rest_framework/settings.py

代码语言:javascript
复制
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
代码语言:javascript
复制
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

代码语言:javascript
复制
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  # 处理之后有值,就直接返回结果

这样我们就可以对异常进行自定义的处理了

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 测试游记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 登录
  • 测试
  • 分析
    • TokenViewBase
      • get_token
        • Token中的for_user
      • update_last_login
      • 异常处理
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档