【问题标题】:Rest_Framework APIClient tests return 401 Unauthorized with Token AuthenticationRest_Framework APIClient 测试返回 401 Unauthorized with Token Authentication
【发布时间】:2015-07-11 00:14:18
【问题描述】:

我正在为我的 rest_framework API 编写一些测试,并且我正在使用令牌身份验证来保护它。我决定使用 DRF 的 APIClient 类来模拟来自用户浏览器的调用。

我可以通过访问身份验证端点从 API 中很好地获取令牌,但是当我尝试使用这些令牌来验证对其他端点的任何进一步请求时,我会返回一个 401 Unauthorized 错误消息,“无效令牌”。

奇怪的是,我可以复制粘贴完全相同的令牌,然后通过 HTTPIE 之类的方式向完全相同的端点发出成功的手动 GET 请求...

这是我的tests.py

import json

from rest_framework import status
from rest_framework.test import APIClient
from rest_framework.test import APITestCase


class TestUser(object):
    """
    A basic user class to simplify requests to the API
    Tokens can be generated by authing as a user to /v1/auth/
    """
    def __init__(self, token):
        self.client = APIClient()
        self.token = token
        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)

    def get(self, url):
        print("Token: {0}".format(self.token))
        res = self.client.get(url)
        print('GET {0}: {1}'.format(url, res.data))
        return res

    def post(self, url, data):
        res = self.client.post(url, data, format='json')
        print('POST {0}: {1}'.format(url, res.data))
        return res

    def patch(self, url, data):
        res = self.client.patch(url, data, json=data)
        print('PATCH {0}: {1}'.format(url, res.data))
        return res

    def delete(self, url):
        res = self.client.delete(url)
        print('DELETE {0}: {1}'.format(url, res.data))
        return res


# Grab new tokens every time we run our tests
auth_client = APIClient()

SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser',
                                      'password': 'password'}).data['token'])
ADMIN = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser4',
                                  'password': 'password'}).data['token'])
MANAGER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser2',
                                    'password': 'password'}).data['token'])
EMPLOYEE = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser3',
                                     'password': 'password'}).data['token'])


class AdminSiteCompanies(APITestCase):
    def test_list_crud_permissions(self):
        # GET
        url = "/v1/admin_site/companies/"
        self.assertEqual(SUPERUSER.get(url).status_code, status.HTTP_200_OK)
        self.assertEqual(ADMIN.get(url).status_code, status.HTTP_200_OK)
        self.assertEqual(MANAGER.get(url).status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(EMPLOYEE.get(url).status_code, status.HTTP_403_FORBIDDEN)

这是上述测试的控制台输出,显示从 API 接收到一个有效令牌,就在我尝试在测试中使用它时它吐回 401 之前:

Creating test database for alias 'default'...
Token: d579dbe4980d8ac451a462fc78cf38f789decddf
GET /v1/admin_site/companies/: {'detail': 'Invalid token.'}
Destroying test database for alias 'default'...

这是我使用 HTTPIE 和上述令牌成功手动 GET 请求的控制台输出:

D:\Projects\API-Server>http http://127.0.0.1:8000/v1/admin_site/companies/ "Authorization: Token d579dbe4980d8ac451a462fc78cf38f789decddf"
HTTP/1.0 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, 01 May 2015 05:43:59 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept
X-Frame-Options: SAMEORIGIN

[
    {
        "address": "1234 Fake Street",
        "id": 1,
        "name": "FedEx",
        "shift_type": "OE"
    },
    {
        "address": "Bolivia",
        "id": 2,
        "name": "UPS",
        "shift_type": "PS"
    }
]

这是我的settings.py 中的相关信息:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'serverapp',
    'rest_framework_swagger',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'serverapp.middlewares.EmployeeMiddleware',
)

ROOT_URLCONF = 'shiftserver.urls'

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework.filters.DjangoFilterBackend',
    )
}

# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

这是我第一次为 Django/rest_framework 编写测试,所以我一直在努力关注DRF's documentation on testing and authenticating。但是,无论我如何尝试,我仍然无法解决这个“无效令牌”问题。

一位比我更精通 DRF 的朋友在我向他寻求帮助时感到困惑,所以希望你们能揭示我们都缺少什么。

【问题讨论】:

    标签: django django-rest-framework


    【解决方案1】:

    我想通了!在 TestCase 类之外发布到 API 会命中我在运行测试时碰巧运行的实际 API 服务器。我重构了AdminSiteCompanies(APITestCase) 来设置测试数据、用户,并在类的setUp(self) 中验证这些用户:

    class AdminSiteCompanies(APITestCase):
        def setUp(self):
            # Create test Objects here
            ...snip...
    
            # Create test Users here
            # SuperUser
            create_user('TestUser', 'password', 'testuser@test.com', True, False, False, co1lo1.id)
            ...snip...
    
            # Grab new tokens every time we run our tests
            # APIClient allows us to emulate calls from a browser
            auth_client = APIClient()
    
            # Authenticate our users
            self.SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser', 'password': 'password'})
                                      .data['token'])
            ...snip...
    
        def test_list_crud_permissions(self):
            # GET
            url = "/v1/admin_site/companies/"
            self.assertEqual(self.SUPERUSER.get(url).status_code, status.HTTP_200_OK)
            # ^ Now passes test
            ...snip...
    

    【讨论】:

      猜你喜欢
      • 2021-08-03
      • 2020-08-15
      • 2020-09-10
      • 1970-01-01
      • 1970-01-01
      • 2022-07-26
      • 2022-11-17
      • 1970-01-01
      • 2016-12-17
      相关资源
      最近更新 更多