如何使用Tastypie登录Django

25

我正在尝试在自定义认证中覆盖is_authenticated。我有一个简单的代码(起始点)如下:

class MyAuthentication(BasicAuthentication):
    def __init__(self, *args, **kwargs):
        super(MyAuthentication, self).__init__(*args, **kwargs)

    def is_authenticated(self, request, **kwargs):
        return True

然后在我的ModelResource中有以下内容:

class LoginUserResource(ModelResource):

    class Meta:
        resource_name = 'login'
        queryset = User.objects.all()
        excludes = ['id', 'email', 'password', 'is_staff', 'is_superuser']
        list_allowed_methods = ['post']

        authentication = MyAuthentication()
        authorization = DjangoAuthorization()

我收到了一个500错误,错误信息为"error_message": "column username is not unique"。数据库中只有一个用户名,并且这个用户名就是我尝试认证的用户。

你们有任何想法为什么会返回这个错误吗?我该如何允许API客户端登录?

感谢您的帮助。


另外,我正在使用sqlite、python 2.7和django 1.4。 - imns
2个回答

70

你的方法会尝试使用你正在进行身份验证的用户名创建一个新用户。正如你所注意到的那样,这将在数据库层面上升,因为这样的用户已经存在。

你想要的是创建一个UserResource,为其添加一个方法,让用户可以通过传递用户名/密码的数据来进行登录。

from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from tastypie.http import HttpUnauthorized, HttpForbidden
from django.conf.urls import url
from tastypie.utils import trailing_slash

class UserResource(ModelResource):
    class Meta:
        queryset = User.objects.all()
        fields = ['first_name', 'last_name', 'email']
        allowed_methods = ['get', 'post']
        resource_name = 'user'

    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/login%s$" %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('login'), name="api_login"),
            url(r'^(?P<resource_name>%s)/logout%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('logout'), name='api_logout'),
        ]

    def login(self, request, **kwargs):
        self.method_check(request, allowed=['post'])

        data = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))

        username = data.get('username', '')
        password = data.get('password', '')

        user = authenticate(username=username, password=password)
        if user:
            if user.is_active:
                login(request, user)
                return self.create_response(request, {
                    'success': True
                })
            else:
                return self.create_response(request, {
                    'success': False,
                    'reason': 'disabled',
                    }, HttpForbidden )
        else:
            return self.create_response(request, {
                'success': False,
                'reason': 'incorrect',
                }, HttpUnauthorized )

    def logout(self, request, **kwargs):
        self.method_check(request, allowed=['get'])
        if request.user and request.user.is_authenticated():
            logout(request)
            return self.create_response(request, { 'success': True })
        else:
            return self.create_response(request, { 'success': False }, HttpUnauthorized)

现在你可以向http://hostname/api/user/login发送POST请求,数据为{ 'username' : 'me', 'password' : 'l33t' }


2
不行,因为资源上唯一公开的字段是 fields = ['first_name', 'last_name', 'email']。此外,登录方法只允许发布,而注销方法只允许获取。 - enticedwanderer
1
您需要为 logout() 应用身份验证,因为它不知道 request.user 并且总是返回 AnonymousUser() - Robert
1
这种方法如何进行CSRF保护? - sanfilippopablo
这样做不会在会话中保留身份验证,对吗? - Dor
字段 request.raw_post_data 自Django 1.4版本起已被弃用,并在Django 1.6中删除。它已更名为 request.body - Ankit Popli
显示剩余5条评论

1

此更新移除GET方法上的安全问题。适用于Django 1.5.4。

class UserResource(ModelResource):

    class Meta:
        queryset = User.objects.all()
        resource_name = 'user'
        allowed_methods = ['post']


    def prepend_urls(self):
        return [
            url(r"^user/login/$", self.wrap_view('login'), name="api_login"),
            url(r"^user/logout/$", self.wrap_view('logout'), name='api_logout'),
        ]

    def login(self, request, **kwargs):
        self.method_check(request, allowed=['post'])

        data = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))

        username = data.get('username', '')
        password = data.get('password', '')

        user = authenticate(username=username, password=password)
        if user:
            if user.is_active:
                login(request, user)
                return self.create_response(request, {
                    'success': True
                })
            else:
                return self.create_response(request, {
                    'success': False,
                    'reason': 'disabled',
                }, HttpForbidden )
        else:
            return self.create_response(request, {
                'success': False,
                'reason': 'incorrect',
                }, HttpUnauthorized )

    def logout(self, request, **kwargs):
        self.method_check(request, allowed=['post'])
        if request.user and request.user.is_authenticated():
            logout(request)
            return self.create_response(request, { 'success': True })
        else:
            return self.create_response(request, { 'success': False }, HttpUnauthorized)

来自 raphodn(声望不足以评论)的快速笔记:关于 ktsw 的好答案,request.raw_post_data 已经被弃用,你应该使用 request.body - Serge Ballesta
1
@ktsw,这个GET请求的安全问题是什么?从所选答案的评论中看来,该答案似乎没有安全问题。"""不会有安全问题,因为资源上唯一公开的字段是fields = ['first_name', 'last_name', 'email']。此外,登录方法只允许POST请求,而注销方法只允许GET请求。""" - Michael SM

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