如何在Django Rest Framework中更新用户密码?

时间:2016-08-09 07:52:06

标签: python django django-rest-framework

我想问下面的代码提供更新密码但我想在当前密码确认过程后更新密码。那么我应该为它添加什么呢?谢谢。

class UserPasswordSerializer(ModelSerializer):

    class Meta:
        model = User
        fields = [
            'password'
        ]

        extra_kwargs = {
            "password": {"write_only": True},
        }

    def update(self, instance, validated_data):
        for attr, value in validated_data.items():
            if attr == 'password':
                instance.set_password(value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

9 个答案:

答案 0 :(得分:47)

我认为使用模型序列化器可能是一种过度杀伤力。这个简单的序列化器&观点应该有效。

Serializers.py

from rest_framework import serializers

class ChangePasswordSerializer(serializers.Serializer):

    """
    Serializer for password change endpoint.
    """
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

Views.py

from rest_framework import status
from rest_framework import generics
from rest_framework.response import Response
from django.contrib.auth.models import User
from . import serializers
from rest_framework.permissions import IsAuthenticated   

class ChangePasswordView(UpdateAPIView):
        """
        An endpoint for changing password.
        """
        serializer_class = ChangePasswordSerializer
        model = User
        permission_classes = (IsAuthenticated,)

        def get_object(self, queryset=None):
            obj = self.request.user
            return obj

        def update(self, request, *args, **kwargs):
            self.object = self.get_object()
            serializer = self.get_serializer(data=request.data)

            if serializer.is_valid():
                # Check old password
                if not self.object.check_password(serializer.data.get("old_password")):
                    return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
                # set_password also hashes the password that the user will get
                self.object.set_password(serializer.data.get("new_password"))
                self.object.save()
                return Response("Success.", status=status.HTTP_200_OK)

            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

答案 1 :(得分:13)

我不认为验证应按照@YiğitGüler的建议进行。这是我的解决方案:

serializers.py

from django.contrib.auth import password_validation
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers

class ChangePasswordSerializer(serializers.Serializer):
    old_password = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)

    def validate_old_password(self, value):
        user = self.context['request'].user
        if not user.check_password(value):
            raise serializers.ValidationError(
                _('Your old password was entered incorrectly. Please enter it again.')
            )
        return value

    def validate(self, data):
        if data['new_password1'] != data['new_password2']:
            raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
        password_validation.validate_password(data['new_password1'], self.context['request'].user)
        return data

    def save(self, **kwargs):
        password = self.validated_data['new_password1']
        user = self.context['request'].user
        user.set_password(password)
        user.save()
        return user

view.py

from rest_framework.generics import UpdateAPIView
from rest_framework.authtoken.models import Token

class ChangePasswordView(UpdateAPIView):
    serializer_class = ChangePasswordSerializer

    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        # if using drf authtoken, create a new token 
        if hasattr(user, 'auth_token'):
            user.auth_token.delete()
        token, created = Token.objects.get_or_create(user=user)
        # return new token
        return Response({'token': token.key}, status=status.HTTP_200_OK)

答案 2 :(得分:11)

@YiğitGüler给出了一个很好的答案,谢谢,但在一些小问题上可能会更好。

只要您不能使用 UpdateModelMixin ,而是直接使用请求用户实例,您就不需要使用 UpdateAPIView 。一个简单的 APIView 就足够了。

此外,更改密码后,您可以使用随机内容返回status.HTTP_204_NO_CONTENT而不是200。

顺便说一下,在保存之前不要忘记验证新密码。如果您在创建时不允许更新“密码”,那就太糟糕了。

所以我在我的项目中使用以下代码:

from django.contrib.auth.password_validation import validate_password

class ChangePasswordSerializer(serializers.Serializer):
    """
    Serializer for password change endpoint.
    """
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

    def validate_new_password(self, value):
        validate_password(value)
        return value

对于观点:

class UpdatePassword(APIView):
    """
    An endpoint for changing password.
    """
    permission_classes = (permissions.IsAuthenticated, )

    def get_object(self, queryset=None):
        return self.request.user

    def put(self, request, *args, **kwargs):
        self.object = self.get_object()
        serializer = ChangePasswordSerializer(data=request.data)

        if serializer.is_valid():
            # Check old password
            old_password = serializer.data.get("old_password")
            if not self.object.check_password(old_password):
                return Response({"old_password": ["Wrong password."]}, 
                                status=status.HTTP_400_BAD_REQUEST)
            # set_password also hashes the password that the user will get
            self.object.set_password(serializer.data.get("new_password"))
            self.object.save()
            return Response(status=status.HTTP_204_NO_CONTENT)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

答案 3 :(得分:9)

保存用户后,您可能希望确保用户保持登录状态(在django == 1.7之后,用户会在密码更改时自动注销):

from django.contrib.auth import update_session_auth_hash

# make sure the user stays logged in
update_session_auth_hash(request, self.object)

答案 4 :(得分:2)

因此,我决定在ModelSerializer中重写更新功能。然后获取用户实例的密码。然后运行必要的比较,以通过check_password函数确保旧密码与当前在用户实例上的密码相同,并确保新密码和确认密码插槽值相同,然后继续设置新密码(如果为true)并保存。实例并返回它。

serializers.py

   class ChangePasswordSerializer(ModelSerializer):
    confirm_password = CharField(write_only=True)
    new_password = CharField(write_only=True)
    old_password = CharField(write_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'old_password', 'new_password','confirm_password']



    def update(self, instance, validated_data):

        instance.password = validated_data.get('password', instance.password)

        if not validated_data['new_password']:
              raise serializers.ValidationError({'new_password': 'not found'})

        if not validated_data['old_password']:
              raise serializers.ValidationError({'old_password': 'not found'})

        if not instance.check_password(validated_data['old_password']):
              raise serializers.ValidationError({'old_password': 'wrong password'})

        if validated_data['new_password'] != validated_data['confirm_password']:
            raise serializers.ValidationError({'passwords': 'passwords do not match'})

        if validated_data['new_password'] == validated_data['confirm_password'] and instance.check_password(validated_data['old_password']):
            # instance.password = validated_data['new_password'] 
            print(instance.password)
            instance.set_password(validated_data['new_password'])
            print(instance.password)
            instance.save()
            return instance
        return instance

views.py

    class ChangePasswordView(RetrieveUpdateAPIView):
        queryset= User.objects.all()
        serializer_class = ChangePasswordSerializer
        permission_classes = [IsAuthenticated]

答案 5 :(得分:1)

我认为最简单(当我说最简单时,我指的是最短和最清洁的)解决方案是:

查看课程

class APIChangePasswordView(UpdateAPIView):
    serializer_class = UserPasswordChangeSerializer
    model = get_user_model() # your user model
    permission_classes = (IsAuthenticated,)

    def get_object(self, queryset=None):
        return self.request.user

序列化器类

from rest_framework import serializers
from rest_framework.serializers import Serializer


class UserPasswordChangeSerializer(Serializer):
    old_password = serializers.CharField(required=True, max_length=30)
    password = serializers.CharField(required=True, max_length=30)
    confirmed_password = serializers.CharField(required=True, max_length=30)

    def validate(self, data):
        # add here additional check for password strength if needed
        if not self.context['request'].user.check_password(data.get('old_password')):
            raise serializers.ValidationError({'old_password': 'Wrong password.'})

        if data.get('confirmed_password') != data.get('password'):
            raise serializers.ValidationError({'password': 'Password must be confirmed correctly.'})

        return data

    def update(self, instance, validated_data):
        instance.set_password(validated_data['password'])
        instance.save()
        return instance

    def create(self, validated_data):
        pass

    @property
    def data(self):
        # just return success dictionary. you can change this to your need, but i dont think output should be user data after password change
        return {'Success': True}

答案 6 :(得分:0)

serializer.py

class UserSer(serializers.ModelSerializers):
      class meta:
          model=UserModel
          fields = '__all__'

views.py

class UserView(UpdateAPIView):
    serializer_class = serializers.UserSer
    queryset = models.User.objects.all()

    def get_object(self,pk):
        try:
            return models.User.objects.get(pk=pk)
        except Exception as e:
            return Response({'message':str(e)})

    def put(self,request,pk,format=None):
        user = self.get_object(pk) 
        serializer = self.serializer_class(user,data=request.data)

        if serializer.is_valid():            
            serializer.save()
            user.set_password(serializer.data.get('password'))
            user.save()
            return Response(serializer.data)    
        return Response({'message':True})

答案 7 :(得分:0)

如果您有ModelViewSet,我想添加另一个选项。这样,您可能希望使用@action进行密码更新,这样您仍然可以使用ModelViewSet处理用户模型的各个方面,并且仍然可以自定义此操作所使用的行为和序列化程序,并且我还将添加一个自定义权限,以验证用户是否尝试更新自己的信息。

permissions.py:

from rest_framework import exceptions
from rest_framework.permissions import BasePermission, SAFE_METHODS
from django.utils.translation import gettext_lazy as _
from users.models import GeneralUser

class IsSelf(BasePermission):
    def has_object_permission(self, request, view, obj):
        if isinstance(obj, GeneralUser):
            return request.user == obj
        raise exceptions.PermissionDenied(detail=_("Received object of wrong instance"), code=403)

*我正在使用我的自定义用户模型类GeneralUser

views.py:

from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework import viewsets
from django.utils.translation import gettext_lazy as _
from users.api.serializers import UserSerializer, UserPwdChangeSerializer
from users.api.permissions import IsSelf

class UserViewSet(viewsets.ModelViewSet):
    __doc__ = _(
        """
        <Your Doc string>
        """
    )
    permission_classes = (IsAuthenticated, IsSelf)
    serializer_class = UserSerializer

    def get_queryset(self):
        return GeneralUser.objects.filter(pk=self.request.user.pk)


    def get_permissions(self):
        if self.action == 'create':
            permission_classes = [AllowAny]
        else:
            permission_classes = [IsAuthenticated]
        return [permission() for permission in permission_classes]

    # ....
    # Your other actions or configurations
    # ....

    @action(detail=True, methods=["put"])
    def upassword(self, request, pk=None):
        user = GeneralUser.objects.get(pk=pk)
        self.check_object_permissions(request, user)
        ser = UserPwdChangeSerializer(user, data=request.data, many=False, context={
            "user":request.user
        })
        ser.is_valid(raise_exception=True)
        user = ser.save()
        return Response(ser.data, status=status.HTTP_200_OK)

serializers.py:

from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password
from django.core import exceptions
from django.contrib.auth.password_validation import validate_password as v_passwords
from rest_framework import serializers

from users.models import GeneralUser

class UserSerializer(serializers.ModelSerializer):
    __doc__ = _(
        """
        Serializer for User model
        """
    )

    class Meta:
        model = GeneralUser
        fields = '__all__'
        read_only_fields = ["last_login", "date_joined"]
        extra_kwargs = {'password': {'write_only': True}}


    def validate_password(self, value: str) -> str:
        try:
            v_passwords(value, GeneralUser)
            return make_password(value)
        except exceptions.ValidationError as e:
            raise serializers.ValidationError(e.messages)


class UserPwdChangeSerializer(serializers.Serializer):
    __doc__ = _(
        """
        Serializer for user model password change
        """
    )

    old_password = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)

    def validate_old_password(self, value):
        user = self.context['user']
        if not user.check_password(value):
            raise serializers.ValidationError(
                _('Your old password was entered incorrectly. Please enter it again.')
            )
        return value

    def validate(self, data):
        if data['new_password1'] != data['new_password2']:
            raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
        v_passwords(data['new_password1'], self.context['user'])
        return data

    def save(self, **kwargs):
        password = self.validated_data['new_password1']
        user = self.context['user']
        user.set_password(password)
        user.save()
        return user

我使用@Pedro的答案来配置UserPwdChangeSerializer


通过此实现,您将拥有一个用于所有字段更新和用户创建的功能ModelViewSet,以及用于密码更新的操作,其中您将能够使用旧密码并验证新密码密码输入正确两次。

自定义密码更改将在您为用户使用的url路径内创建,可能类似于:

api/users/<user_pk>/upassword

答案 8 :(得分:0)


<块引用>

编辑:使用 capcha 或类似的东西来逃避蛮力......


我用我自己的hacky方式做到了! 可能不是最好的方法,但我发现它更好理解,,,

** 随意询问是否有任何东西似乎是保镖,我总是鼓励问题和反馈...... **

我为它创建了一个模型。

class PasswordReset(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    key = models.CharField(max_length=100)
    timestamp = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

添加了这样的网址...

urlpatterns = [
    path("request/", password_reset_request),
    path("confirm/", password_reset_confirm),
]

这里有我们的观点...

@api_view(["POST"])
@permission_classes([AllowAny])
def password_reset_request(request):
    # checking username
    queryset = User.objects.filter(username=request.POST.get("username"))
    if queryset.exists():
        user = queryset.first()
    else:
        return Response({"error": "User does not exists!"})

    # Checking for password reset model
    queryset = PasswordReset.objects.filter(user=user)
    if queryset.exists():
        password_reset = PasswordReset.first()
        # checking for last password reset
        if password_reset.timestamp < timezone.now() - timedelta(days=1):
            # password is not recently updated
            password_reset.delete()
            password_reset = PasswordReset(
                user=user,
                key="".join(
                    [choice("!@$_-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") for i in range(99)]
                ),
            )
            password_reset.save()

            # send email here
            subject = "Password reset request"
            message = """To reset your password, go to localhost:8000/password_reset/{}""".format(password_reset.key)
            from_email = "notes@frozenmatrix.com"
            recipient_list = [user.email]
            auth_user = "digital.mechanics.00@gmail.com"
            auth_password = "mechanicsareawesomeagain"
            send_mail(subject, message, from_email, recipient_list, auth_user=auth_user, auth_password=auth_password)

        else:
            # recent password updated
            return Response({"error": "Your password was updated recently, wait before updating it again."})


@api_view(["POST"])
@permission_classes([AllowAny])
def password_reset_confirm(request):
    # checking key
    queryset = PasswordReset.objects.filter(key=request.POST.get("key"))
    if queryset.exists():
        password_reset = queryset.first()

        if password_reset.timestamp < timezone.now() - timedelta(minutes=30):
            # expired
            return Response({"error": "Password reset key is expired! Try fresh after some hours."})

        else:
            # valid
            password = request.POST.get("password", "")
            if password == "":
                # valid key and waiting for password
                return Response({"success": "Set a new password"})

            else:
                # seting up the password
                user = password_reset.user
                user.set_password(password)
                user.save()
                return Response({"success": "Password updated successfully."})

    else:
        # invalid key
        return Response({"error": "Invalid key"})