Django rest 框架 JWT 和自定义身份验证后端

Posted

技术标签:

【中文标题】Django rest 框架 JWT 和自定义身份验证后端【英文标题】:Django rest framework JWT and custom authentication backend 【发布时间】:2017-10-27 19:53:51 【问题描述】:

我有一个自定义用户模型并创建了一个自定义身份验证后端。我正在使用django rest framework 和django rest framework JWT 进行令牌认证。

用户模型:

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(
        unique=True,
        max_length=254,
    )
    first_name = models.CharField(max_length=15)
    last_name = models.CharField(max_length=15)
    mobile = models.IntegerField(unique=True)
    date_joined = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    objects = UserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name', 'mobile']

身份验证后端:

class EmailOrMobileAuthBackend(object):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

    def get_user(self, user_id):
        try:
            return get_user_model().objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

并且已经在settings.py中添加了:

AUTHENTICATION_BACKENDS = ('accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',)

登录 django 管理站点时,电子邮件和手机号码都可以很好地验证用户身份。但是,当我尝试使用django rest framework JWT 为用户获取令牌时,出现错误:

curl -X POST -d "email=admin@gmail.com&password=123123" http://localhost/api-token-auth/

"non_field_errors": [
    "Unable to log in with provided credentials."
  ]

我还在 Rest 框架的默认身份验证中添加了自定义身份验证后端类,但它仍然无法正常工作:

REST_FRAMEWORK = 
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),

我错过了什么?为什么在登录django管理站点时它可以工作,但是在使用django rest framework jwt获取令牌时出错?

更新

我按照建议创建了另一个身份验证后端并将其添加到DEFAULT_AUTHENTICATION_CLASSES,但即使这样也不起作用。

class DrfAuthBackend(BaseAuthentication):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user, None
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user, None
                except User.DoesNotExist:
                    return None
            else:
                return None

设置:

REST_FRAMEWORK = 
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',
        'accounts.email_mobile_auth_backend.DrfAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),

更新

将身份验证类中的argsusername 更改为email 似乎可以获取auth_token,但同样无法登录到管理站点。

class EmailOrMobileAuthBackend(object):
    def authenticate(self, email=None, password=None):
        try:
            user = get_user_model().objects.get(email=email)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if email.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=email)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

    def get_user(self, user_id):
        try:
            return get_user_model().objects.get(pk=user_id)
        except User.DoesNotExist:
            return None 

【问题讨论】:

您最终解决了这个问题吗?在使用django-rest-frameworkdjango-rest-framework-jwt 时遇到了类似的问题,我需要允许用户通过用户名或电子邮件或mobile_number 登录来获取JWT 令牌 @lukik 我正在使用两个身份验证后端。一个用于 django 管理员,另一个用于其余 api。 这两个后端是否允许用户通过电子邮件或手机登录?如果是的话,你介意分享一下你是怎么做的,尤其是对于其余的 API 吗?谢谢。 @lukik 是的,当然。我创建了两个身份验证后端(两者基本相同)。你可以找到他们here 和there。然后在设置中我沿线添加了these。 【参考方案1】:

您应该检查 DRF documentation 以获取自定义身份验证后端。

我认为您的自定义身份验证后端破坏了它,您可以通过从 DRF 设置中删除您的来解决它:

REST_FRAMEWORK = 
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),

或者通过修复你的,这与 Django 自定义身份验证基本不同,因为你应该从 authentication.BaseAuthentication 扩展并返回一个元组。

from rest_framework import authentication


class DrfAuthBackend(authentication.BaseAuthentication):
    def authenticate(self, email=None, password=None):
        try:
            user = get_user_model().objects.get(email=email)
            if user.check_password(password):
                return user, None
        except User.DoesNotExist:
            if email.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=email)
                    if user.check_password(password):
                        return user, None
                except User.DoesNotExist:
                    return None
            else:
                return None

然后在 DRF 设置中使用它:

REST_FRAMEWORK = 
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'accounts.email_mobile_auth_backend.DrfAuthBackend',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),

对于 Django 登录:

class EmailOrMobileAuthBackend(object):
    def authenticate(self, username=None, password=None):
        try:
            user = get_user_model().objects.get(email=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            if username.isdigit():
                try:
                    user = get_user_model().objects.get(mobile=username)
                    if user.check_password(password):
                        return user
                except User.DoesNotExist:
                    return None
            else:
                return None

    def get_user(self, user_id):
        try:
            return get_user_model().objects.get(pk=user_id)
        except User.DoesNotExist:
            return None 

然后进行设置:

AUTHENTICATION_BACKENDS = ('accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend',)

【讨论】:

我已经尝试了您的建议并更新了问题,但仍然无法正常工作。但是,将authentication() 方法中的参数从username 更改为email 无需将其添加到DEFAULT_AUTHENTICATION_CLASSES 即可完成工作。但同样无法登录到管理站点。 The Django custom authentication 不支持电子邮件作为关键字参数,您应该只将它用于 Django 并为 DRF 写一个不同的【参考方案2】:

在您的 settings.py 文件中,添加以下内容:

AUTH_USER_MODEL = 'accounts.email_mobile_auth_backend.EmailOrMobileAuthBackend'

【讨论】:

以上是关于Django rest 框架 JWT 和自定义身份验证后端的主要内容,如果未能解决你的问题,请参考以下文章

Django 1.5:UserCreationForm 和自定义身份验证模型

关于路径和自定义装饰器的 Python3 Django 问题

使用 otp 获取 api 令牌的 Django REST 框架的 JWT 身份验证

JWT 身份验证不适用于 Django 中的自定义控制器

如何使 AzureAD 和自定义 JWT 令牌在 Web API 中并行工作?

Django Rest Framework 中的 JWT 身份验证错误“无效签名”