django rest framework 自定义token

python rest framework 自定义标题

rest framework 中的自带token是基自身模型user 设定的,模型中的字段,pk键等可能跟实际应用中有很大的区别,本文简单介绍如何使用自定user模型,生成token,并验证。

参考文档

感谢!!

1 准备

基于【适合小白的Django rest_framework Token入门】 第一篇参考文档进行,

2 go

原生token基于用户的用户名和密码,本文中将基于用户手机号和密码,生成token

2.1 项目结构

在这里插入图片描述

2.2 用户model

import binascii
import os

from django.db import models
from django.utils.translation import gettext_lazy as _

from django.contrib.auth.hashers import (
    check_password, is_password_usable, make_password,
)
# Create your models here.

class UserModel(models.Model):
    name = 'sl_users'
    _password = None
    id_user = models.AutoField('业主id', primary_key=True)
    users_user_name = models.CharField('业主姓名', max_length=50, default='您的姓名')
    users_user_tel = models.CharField('业主手机号码', max_length=13, unique=True)
    users_open_id = models.CharField('业主微信openid', max_length=100, null=True)
    users_user_pass = models.CharField('业主登录密码', max_length=255, default=None)
    users_is_valid = models.IntegerField('业主是否有效', default=0)

    @property
    def is_authenticated(self):
        """
        Always return True. This is a way to tell if the user has been
        authenticated in templates.
        """
        return True
    def set_password(self, raw_password):
        self.users_user_pass = make_password(raw_password)
        self._password = raw_password

    def check_password(self, raw_password):
        """
        Return a boolean of whether the raw_password was correct. Handles
        hashing formats behind the scenes.
        """

        def setter(raw_password):
            self.set_password(raw_password)
            # Password hash upgrades shouldn't be considered password changes.
            self._password = None
            self.save(users_user_pass=["password"])

        return check_password(raw_password, self.users_user_pass, setter)

    def has_usable_password(self):
        """
        Return False if set_unusable_password() has been called for this user.
        """
        return is_password_usable(self.users_user_pass)

# 数据库中 存储token的表结构
class Token(models.Model):
    """
    The custom authorization token model.
    """
    key = models.CharField(_("Key"), max_length=40, primary_key=True)
    user = models.OneToOneField(
        UserModel, related_name='auth_token',
        on_delete=models.CASCADE, verbose_name=_("User")
    )
    created = models.DateTimeField(_("Created"), auto_now_add=True)

    class Meta:
        # Work around for a bug in Django:
        # https://code.djangoproject.com/ticket/19422
        #
        # Also see corresponding ticket:
        # https://github.com/encode/django-rest-framework/issues/705
        verbose_name = _("Token")
        verbose_name_plural = _("Tokens")

    def save(self, *args, **kwargs):
        if not self.key:
            self.key = self.generate_key()
        return super().save(*args, **kwargs)

    def generate_key(self):
        return binascii.hexlify(os.urandom(20)).decode()

    def __str__(self):
        return self.key

2.3 views.py

/myToken/views.py

from rest_framework.decorators import api_view
from django.http import JsonResponse
from django.shortcuts import HttpResponse
# Create your views here.

from rest_framework import viewsets
from myToken.models.users import UserModel, Token
from django.contrib import auth


from myToken.serializers.userSerializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = UserModel.objects.all()
    serializer_class = UserSerializer

@api_view(['POST'])
def login(request):
    receive = request.data
    username = receive.get('username')
    password = receive.get('password')

    user = auth.authenticate(user_tel=username, password=password)
    if not user:
        return HttpResponse({"用户名和密码不匹配"})
    old_token = Token.objects.filter(user=user)
    old_token.delete()
    # 创建新的Token
    token = Token.objects.create(user=user)
    return JsonResponse({"username": user.users_user_name, "token": token.key})

@api_view(['POST'])
def doSomething(request):
    receive = request.data
    print(receive)
    if request.user.is_authenticated:   # 验证Token是否正确
        print("Do something...")
        return JsonResponse({"msg": "验证通过"})
    else:
        print("验证失败")
        return JsonResponse({"msg": "验证失败"})

2.4 urls.py 路由

/tokenTest/urls.py

from myToken.views import UserViewSet, login, doSomething
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
    path('login/', login),
    path('doSomething/', doSomething)
]

2.5 自定义auth.py

/tokenTest/auth.py

import datetime
from django.utils.translation import ugettext_lazy
from django.core.cache import cache
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from myToken.models.users import Token
from rest_framework import HTTP_HEADER_ENCODING

# 获取请求头信息
def get_authorization_header(request):
    #auth = request.META.get('HTTP_AUTHORIZATION', b'')
    # 自定义 header中的 key  即AUTHORIZATION 改名为token
    auth = request.META.get('HTTP_TOKEN', None)

    if isinstance(auth, type('')):
        auth = auth.encode(HTTP_HEADER_ENCODING)    return auth


# 自定义认证方式,这个是后面要添加到设置文件的
class ExpiringTokenAuthentication(BaseAuthentication):
    model = Token
    def authenticate(self, request):
        auth = get_authorization_header(request)

        if not auth:
            return None
        try:
            token = auth.decode()
        except UnicodeError:
            msg = ugettext_lazy("无效的Token, Token头不应包含无效字符")
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(token)

    def authenticate_credentials(self, key):
        # 尝试从缓存获取用户信息(设置中配置了缓存的可以添加,不加也不影响正常功能)
        token_cache = 'token_' + key
        cache_user = cache.get(token_cache)
        if cache_user:
            return cache_user, cache_user  # 这里需要返回一个列表或元组,原因不详
        # 缓存获取到此为止

        # 下面开始获取请求信息进行验证
        try:
            token = self.model.objects.get(key=key)
        except self.model.DoesNotExist:
            raise exceptions.AuthenticationFailed("认证失败")

        if not token.user.users_is_valid:
            raise exceptions.AuthenticationFailed("用户被禁用")

        # Token有效期时间判断(注意时间时区问题)
        # 我在设置里面设置了时区 USE_TZ = False,如果使用utc这里需要改变。
        if (datetime.datetime.now() - token.created) > datetime.timedelta(hours=0.1 * 1):
            raise exceptions.AuthenticationFailed('认证信息已过期')

        # 加入缓存增加查询速度,下面和上面是配套的,上面没有从缓存中读取,这里就不用保存到缓存中了
        if token:
            token_cache = 'token_' + key
            cache.set(token_cache, token.user, 600)

        # 返回用户信息
        return token.user, token

    def authenticate_header(self, request):
        return 'Token'

2.6 在项目目录下添加文件myBackend.py

/tokenTest/myBackend.py

from myToken.models.users import UserModel  #自定义的用户模型

from django.contrib.auth.backends import BaseBackend

class MyModelBackend(BaseBackend):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def authenticate(self, request, user_tel=None, password=None, **kwargs):
        if user_tel is None:
            user_tel = kwargs.get(UserModel.users_user_tel)
        if user_tel is None or password is None:
            return
        try:
            user = UserModel.objects.get(users_user_tel=user_tel)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None


    def user_can_authenticate(self, user):
        """
        Reject users with is_active=False. Custom user models that don't have
        that attribute are allowed.
        """
        is_active = getattr(user, 'users_is_valid', None)  # 自定义模型中表示用户是否有效的字段
        return is_active or is_active is None

2.7 修改项目settings.py, 添加

/tokenTest/urls.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'tokenTest.auth.ExpiringTokenAuthentication',   # 根据自己的实际情况填写路径
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),
}
AUTHENTICATION_BACKENDS = ('tokenTest.myBackend.MyModelBackend', )

上述代码 删除了 自带的Backend,将使用我们自己定义的Backend处理业务

3 测试结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上!

最后 , 源码是个好东西,读源码 然后修改!!

本案源码下载

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yutao1131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值