Django Rest使用用户名或电子邮件进行JWT登录?

20
我在我的应用程序中使用django-rest-jwt进行身份验证。默认情况下,它使用用户名字段来验证用户,但我希望用户可以使用电子邮件或用户名登录。django-rest-jwt是否支持此功能?如果不支持,最后的选择是编写自己的登录方法。
7个回答

30

无需编写自定义身份验证后端或自定义登录方法。

可以通过继承JSONWebTokenSerializer并重命名'username_field'以及覆盖def validate()方法的自定义序列化程序来实现。在此方案中,用户可以输入其用户名或电子邮件地址,并获取正确凭据的JSONWebToken,适用于'username_or_email'和'password'字段。

from rest_framework_jwt.serializers import JSONWebTokenSerializer

from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import ugettext as _
from rest_framework import serializers

from rest_framework_jwt.settings import api_settings


User = get_user_model()
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER

class CustomJWTSerializer(JSONWebTokenSerializer):
    username_field = 'username_or_email'

    def validate(self, attrs):

        password = attrs.get("password")
        user_obj = User.objects.filter(email=attrs.get("username_or_email")).first() or User.objects.filter(username=attrs.get("username_or_email")).first()
        if user_obj is not None:
            credentials = {
                'username':user_obj.username,
                'password': password
            }
            if all(credentials.values()):
                user = authenticate(**credentials)
                if user:
                    if not user.is_active:
                        msg = _('User account is disabled.')
                        raise serializers.ValidationError(msg)

                    payload = jwt_payload_handler(user)

                    return {
                        'token': jwt_encode_handler(payload),
                        'user': user
                    }
                else:
                    msg = _('Unable to log in with provided credentials.')
                    raise serializers.ValidationError(msg)

            else:
                msg = _('Must include "{username_field}" and "password".')
                msg = msg.format(username_field=self.username_field)
                raise serializers.ValidationError(msg)

        else:
            msg = _('Account with this email/username does not exists')
            raise serializers.ValidationError(msg)

在urls.py文件中:

url(r'{Your url name}$', ObtainJSONWebToken.as_view(serializer_class=CustomJWTSerializer)),

13

在Shikhar的回答基础上,对于任何来到这里寻找 rest_framework_simplejwt 解决方案的人(因为 django-rest-framework-jwt 似乎已经死了,它最后的提交是两年前),这里提供一个通用解决方案,试图尽可能少地更改TokenObtainPairSerializer的原始验证:

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomJWTSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        credentials = {
            'username': '',
            'password': attrs.get("password")
        }

        # This is answering the original question, but do whatever you need here. 
        # For example in my case I had to check a different model that stores more user info
        # But in the end, you should obtain the username to continue.
        user_obj = User.objects.filter(email=attrs.get("username")).first() or User.objects.filter(username=attrs.get("username")).first()
        if user_obj:
            credentials['username'] = user_obj.username

        return super().validate(credentials)

而在urls.py中:

url(r'^token/$', TokenObtainPairView.as_view(serializer_class=CustomJWTSerializer)),


谢谢,这让我意识到 django-rest-framework-jwt 已经不再维护了,这非常重要! - piritocle
我在代码中将“username”更改为“email”,然后我可以在登录期间使用“email”字段。我还必须将“username_field ='email'”设置为类变量。 - Loránd Péter

1
发现了一种解决方法。
@permission_classes((permissions.AllowAny,))
def signin_jwt_wrapped(request, *args, **kwargs):
    request_data = request.data
    host = request.get_host()
    username_or_email = request_data['username']
    if isEmail(username_or_email):
        # get the username for this email by model lookup
        username = Profile.get_username_from_email(username_or_email)
        if username is None:
            response_text = {"non_field_errors":["Unable to login with provided credentials."]}
            return JSONResponse(response_text, status=status.HTTP_400_BAD_REQUEST)
    else:
        username = username_or_email

    data = {'username': username, 'password':request_data['password']}
    headers = {'content-type': 'application/json'}
    url = 'http://' + host + '/user/signin_jwt/'
    response = requests.post(url,data=dumps(data), headers=headers)

    return JSONResponse(loads(response.text), status=response.status_code)

我检查接收到的文本是用户名还是电子邮件。
如果是电子邮件,则查找该电子邮件对应的用户名,然后将其传递给/signin_jwt/

1

authentication.py

from django.contrib.auth.models import User

class CustomAuthBackend(object):
    """
    This class does the athentication-
    using the user's email address.
    """
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(password):
                return user
            return None
        except User.DoesNotExist:
            return None

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

settings.py

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'app_name.authentication.CustomAuthBackend',
]

工作原理:

如果用户使用用户名进行身份验证,则Django将查看ModelBackend类。但是,如果用户改为使用电子邮件进行身份验证,则Django将尝试ModelBackend,但不会找到所需的逻辑,然后将尝试CustomAuthBackend类,从而使身份验证正常工作。


0
如果您使用rest_framework_simplejwt,这是一种简单模式。views.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.http import JsonResponse
from rest_framework import generics

class EmailAuthToken(generics.GenericAPIView):
 def post(self, request):

    user_data = request.data
    try:
        user = authenticate(request, username=user_data['username_or_email'], password=user_data['password'])
        if user is not None:
            login(request, user)

            refresh = RefreshToken.for_user(user)

            return JsonResponse({
                'refresh': str(refresh),
                'access': str(refresh.access_token),
            }, safe=False, status=status.HTTP_200_OK)
        else:
            return JsonResponse({
                    "detail": "No active account found with the given credentials"
                }, safe=False, status=status.HTTP_200_OK)
    except:
            return Response({'error': 'The format of the information is not valid'}, status=status.HTTP_401_UNAUTHORIZED)


0

0
  1. dj-rest-auth 在身份验证和授权方面表现更好。默认情况下,dj-rest-auth 提供用户名、电子邮件和密码字段进行登录。用户可以提供电子邮件和密码或用户名和密码。如果提供的值有效,则会生成令牌。

  2. 如果您需要编辑这些登录表单,请扩展 LoginSerializer 并修改字段。然后确保将新的自定义序列化程序添加到 settings.py 中。

    REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': 'yourapp.customlogin_serializers.CustomLoginSerializer' }

  3. 配置 dj-rest-auth 有点棘手,因为它存在一个有关刷新令牌的未解决问题。针对该问题提出了一种解决方法,因此您可以按照以下链接进行配置。

https://medium.com/geekculture/jwt-authentication-in-django-part-1-implementing-the-backend-b7c58ab9431b

https://github.com/iMerica/dj-rest-auth/issues/97


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接