【问题标题】:django-rest-framework returning 403 response on POST, PUT, DELETE despite AllowAny permissions尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应
【发布时间】: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 请求发送该标头,一切正常。

【问题讨论】:

    标签: django authentication permissions django-rest-framework


    【解决方案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。你能详细说明你的意思吗?
      猜你喜欢
      • 2020-10-07
      • 2015-11-16
      • 2013-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多