尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应

Posted

技术标签:

【中文标题】尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应【英文标题】:django-rest-framework returning 403 response on POST, PUT, DELETE despite AllowAny permissions 【发布时间】:2015-12-15 16:35:50 【问题描述】:

我使用django-oneall 允许在我的网站上进行社交登录会话身份验证。虽然它不是 django-rest-framework 的建议身份验证提供程序之一,但rest_framework.authentication.SessionAuthentication 使用 django 的默认会话身份验证。所以我认为集成应该相当简单。

在权限方面,最终我将使用IsAdmin,但出于开发目的,我只是将其设置为IsAuthenticated。当返回 403 时,我将权限放宽到 AllowAny,但仍然没有骰子。这是我的休息框架配置:

settings.py

REST_FRAMEWORK = 
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
        # 'rest_framework.permissions.IsAuthenticated',
        # 'rest_framework.permissions.IsAdminUser',
     ),
    'PAGE_SIZE': 100,
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework.filters.DjangoFilterBackend',
    ),

编辑:

我根据下面的答案得到了这个工作。事实证明,rest_framework 需要 csrftoken cookie 和 X-CSRFToken 相同值的标头,我设置前端代码为所有 ajax 请求发送该标头,一切正常。

【问题讨论】:

【参考方案1】:

Django REST Framework 在几种相关情况下返回状态码403

当您没有所需的权限级别时(例如,当 DEFAULT_PERMISSION_CLASSES('rest_framework.permissions.IsAuthenticated',) 时,以未经身份验证的用户身份发出 API 请求。 当您执行不安全的请求类型(POST、PUT、PATCH 或 DELETE - 应该有副作用的请求)时,您使用的是rest_framework.authentication.SessionAuthentication,并且您没有在请求集中包含您的 CSRFToken。 当您执行不安全的请求类型并且您包含的 CSRFToken 不再有效时。

我将针对测试 API 发出一些演示请求,以提供每个示例,以帮助您诊断遇到的问题并展示如何解决它。我将使用requests 库。

测试 API

我使用单个模型 Life 设置了一个非常简单的 DRF API,其中包含单个字段(answer,默认值为 42)。从现在开始的一切都非常简单。我在/life URL 路由上设置了ModelSerializer - LifeSerializerModelViewSet - LifeViewSetDefaultRouter。我已将 DRF 配置为要求用户通过身份验证才能使用 API 并使用 SessionAuthentication

使用 API

import json
import requests

response = requests.get('http://localhost:8000/life/1/')
# prints (403, '"detail":"Authentication credentials were not provided."')
print response.status_code, response.content

my_session_id = 'mph3eugf0gh5hyzc8glvrt79r2sd6xu6'
cookies = 
cookies['sessionid'] = my_session_id
response = requests.get('http://localhost:8000/life/1/',
                        cookies=cookies)
# prints (200, '"id":1,"answer":42')
print response.status_code, response.content

data = json.dumps('answer': 24)
headers = 'content-type': 'application/json'
response = requests.put('http://localhost:8000/life/1/',
                        data=data, headers=headers,
                        cookies=cookies)
# prints (403, '"detail":"CSRF Failed: CSRF cookie not set."')
print response.status_code, response.content

# Let's grab a valid csrftoken
html_response = requests.get('http://localhost:8000/life/1/',
                             headers='accept': 'text/html',
                             cookies=cookies)
cookies['csrftoken'] = html_response.cookies['csrftoken']
response = requests.put('http://localhost:8000/life/1/',
                        data=data, headers=headers,
                        cookies=cookies)
# prints (403, '"detail":"CSRF Failed: CSRF token missing or incorrect."')
print response.status_code, response.content

headers['X-CSRFToken'] = cookies['csrftoken']
response = requests.put('http://localhost:8000/life/1/',
                        data=data, headers=headers,
                        cookies=cookies)
# prints (200, '"id":1,"answer":24')
print response.status_code, response.content

【讨论】:

hmm... 我的 Ember 应用确实在请求中传递了 csrf cookie。我在 apache 中为我的 rest api 设置了一个反向代理。也许我需要以某种方式修改我的反向代理,以便将 csrf cookie 值复制到 HTTP_X_CSRFTOKEN 标头中? 我想我宁愿将 ember 配置为始终在标头中传递 csrftoken cookie 值。这应该更容易。 @ckot 我已经更新了我的答案。忘了标头 X-CSRFToken 和 cookie csrftoken 都必须设置。 谢谢。太棒了,这完全有道理。我会尝试一下,我需要先研究配置 ember 位。我想这只是一个$.ajaxSetup() 或类似的东西 是的。成功了,谢谢!你无法想象你为我节省了多少时间。现在我可以使用IsAuthenticated privs。一旦我更新了我的社交登录/注册页面,以便它可以区分管理员和普通用户,我就可以切换到IsAdmin【参考方案2】:

仅适用于可能发现相同问题的任何人。 如果您使用没有路由器的视图集,例如:

user_list = UserViewSet.as_view('get': 'list')
user_detail = UserViewSet.as_view('get': 'retrieve')

除非您在类级别定义 permission_classes,否则 Django Rest 框架将返回 403:

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset for viewing and editing user instances.
    """
    permission_classes= YourPermisionClass

希望对你有帮助!

【讨论】:

【参考方案3】:

为了完整起见,DRF 在另一种情况下返回代码 403:如果您忘记在 urls.py 文件中的视图声明中添加 as_view()。刚刚发生在我身上,我花了几个小时才找到问题所在,所以也许这个添加可以为某人节省一些时间。

【讨论】:

as_view 用于基于 django 类的视图,据我所知,它与 django-rest-framework 中的 api-url 无关。 DRF 也没有在他们的路由文档中提到它(除了包括非 DRF 视图)django-rest-framework.org/api-guide/routers。你能详细说明你的意思吗?

以上是关于尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应的主要内容,如果未能解决你的问题,请参考以下文章

drf的权限扩充

权限组件permission

Django Rest Framework 请求对 AllowAny 设置进行身份验证

尽管有权限,但 mongod 权限被拒绝

尽管我有管理员权限,但 Spring Boot 返回 403

尽管获得许可,但写入外部存储的权限被拒绝