python-3.x DRF要求用户登录才能使用login_user API

cgyqldqp  于 12个月前  发布在  Python
关注(0)|答案(2)|浏览(106)

我试图创建一个CustomUserViewset,并添加一个login_user API到它。问题是,尽管我将permission_classes设置为AllowAny,但在调用login_user API时,它仍然显示:{"detail":"Please login to perform this action"}
以下是我的API:

class CustomUserViewset(AutoPermissionViewSetMixin, viewsets.ModelViewSet):
    queryset = User.objects.none()
    serializer_class = CustomUserSerializer

    permission_type_map = {
        "create": "add",
        "destroy": "delete",
        "partial_update": "change",
        "retrieve": "view",
        "update": "change",
        "register": "view",
        "login_user": "view",
        "logout": "change",
    }

    @decorators.action(methods=["POST"], detail=False, permission_classes=[permissions.AllowAny])
    def login_user(self, request):
        serializer = LoginSerializer(data=request.data)

        if not serializer.is_valid():
            raise exceptions.ValidationError({"detail": "Invalid username or password"})

        username = serializer.validated_data["username"]
        password = serializer.validated_data["password"]

        user = authenticate(request, username=username, password=password)

        if user is not None:
            login(request, user)
            return Response(CustomUserSerializer(user).data, status=status.HTTP_200_OK)
        else:
            raise exceptions.AuthenticationFailed({"detail": "Invalid username or password"})

字符串
正如你所看到的,我在API action中有permission_classes=[permissions.AllowAny]。此外,在action中赋予此权限类是我尝试的最后一件事,在此之前,我尝试在rules.py中调整权限:

import typing

import rules

if typing.TYPE_CHECKING:
    from .models import User
rules.add_perm("accounts.login_user", rules.predicates.always_allow)


以上方法都不起作用,我仍然得到相同的消息,我需要登录才能执行此操作。

zf9nrax1

zf9nrax11#

  • 更新答案 *

我解决了这个问题,首先向permission_type_map添加了一些权限,然后调整了rules.py:

class CustomUserViewset(AutoPermissionViewSetMixin, viewsets.ModelViewSet):
    queryset = User.objects.none()
    serializer_class = serializers.CustomUserSerializer

    permission_type_map = {
        "list": "list",
        "create": "add",
        "destroy": "delete",
        "partial_update": "change",
        "retrieve": "view",
        "update": "change",
        "register": "register",
        "login": "login",
        "logout": "logout",
    }

    @decorators.action(methods=["post"], detail=False)
    def register(self, request):
        register_serializer = serializers.RegisterSerializer(data=request.data)

        register_serializer.is_valid(raise_exception=True)

        username = register_serializer.validated_data["username"]
        password = register_serializer.validated_data["password"]
        # email = register_serializer.validated_data["email"] // for now, email is auto-generated
        email = f"{username}@example.com"

        new_user = User.objects.create_user(username=username, email=email, password=password)
        user_serializer = serializers.CustomUserSerializer(new_user)
        return Response(user_serializer.data, status=status.HTTP_201_CREATED)

    @decorators.action(methods=["post"], detail=False)
    def login(self, request):
        login_serializer = serializers.LoginSerializer(data=request.data)

        login_serializer.is_valid(raise_exception=True)

        username = login_serializer.validated_data["username"]
        password = login_serializer.validated_data["password"]

        user = authenticate(request, username=username, password=password)

        if user:
            login(request, user)
            return Response(
                serializers.CustomUserSerializer(user).data,
                status=status.HTTP_200_OK,
            )
        raise exceptions.AuthenticationFailed({"detail": "Invalid username or password"})

    @decorators.action(detail=False, methods=["get"], permission_classes=[permissions.IsAuthenticated])
    def logout(self, request):
        logout(request)
        return Response({"detail": "Successfully logged out"})

字符串
在rules.py中:

import typing

import rules

if typing.TYPE_CHECKING:
    from .models import User

@rules.predicate(bind=True)
def is_active(self, user: "User"):
    return getattr(user, "is_active", False)

@rules.predicate(bind=True)
def is_admin(self, user: "User"):
    return is_active(user) if user.is_superuser else False

@rules.predicate(bind=True)
def is_staff(self, user: "User"):
    return is_active(user) if user.is_staff or user.is_superuser else False

@rules.predicate(bind=True)
def is_same_user(self, auth_user: "User", target_user: "User"):
    """
    Is authenticated user the same user as target_user?
    """
    if auth_user and target_user:
        print("auth_user:", auth_user)
        print("target_user:", target_user)
        return auth_user.pk == target_user.pk
    return False

rules.add_perm("accounts.view_user", is_admin | is_same_user)
rules.add_perm("accounts.list_user", is_active)
rules.add_perm("accounts.change_user", is_admin | is_same_user)
rules.add_perm("accounts.add_user", is_admin)
rules.add_perm("accounts.delete_user", is_admin)
rules.add_perm("accounts.login_user", rules.predicates.always_allow)
rules.add_perm("accounts.register_user", rules.predicates.always_allow)
rules.add_perm("accounts.logout_user", is_active)


现在有了这些新的权限和规则,所有三个API都可以按预期工作。

niknxzdl

niknxzdl2#

也许装饰器没有正确地覆盖它,但我不能确认它。但这里有另一种替代方法可以使用。下面的例子将登录名分离到另一个类,这样访问权限就不会混淆,它只处理一件事。
首先,创建自定义视图集以仅处理此登录:

from rest_framework import status, viewsets
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from custom_user.models import CustomUser
from custom_user.serializers import CustomUserSerializer

class CustomUserLoginViewSet(viewsets.ViewSet):
    serializer_class = CustomUserLoginSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        data = request.data
        if CustomUser.objects.filter(username=data["username"]).exists():
            user: CustomUser = CustomUser.objects.get(
                username=data["username"])
            if user.check_password(data["password"]):
                refresh = RefreshToken.for_user(user)
                return Response(
                    {
                        "data": {
                            "user": CustomUserSerializer(user).data,
                            "token": str(refresh.access_token),
                            "refresh": str(refresh),
                        }
                    },
                    status=status.HTTP_200_OK,
                )
            else:
                return Response(
                    {"message": "Invalid Password"}, status=status.HTTP_400_BAD_REQUEST
                )
        else:
            return Response(
                {"message": "User Does Not Exist"}, status=status.HTTP_400_BAD_REQUEST
            )

字符串
然后手动调用它在你的project/urls.py像这样:

# ... other import
from django.urls import include, path
from custom_user.views import CustomUserLoginViewSet

urlpatterns = [
    # ... other path
    path("login/", CustomUserLoginViewSet.as_view({"post": "post"})),
    # ... other path
]


不要忘记更新你的导入路径或者登录的核心代码,因为我的是djangorestframework-simplejwt。这只是一个例子,但是它工作正常。

相关问题