这里写自定义目录标题
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 测试结果
以上!
最后 , 源码是个好东西,读源码 然后修改!!