【问题标题】:Getting Django, VUE, CORS and CSRF working with a real world example让 Django、VUE、CORS 和 CSRF 与现实世界的示例一起工作
【发布时间】:2019-07-17 02:19:42
【问题描述】:

我真的被困住了。这就是我想要做的。

  1. 保持 CSRF 开启。 - 请不要告诉我关掉它。
  2. 我有一个由 Django 和 Django Rest Framework 运行的 API 应用程序
  3. 我有一个由 Vue 运行的前端应用程序
  4. 我已经安装了 django-cors-headers 来管理 CORS

在本地一切都很好。一旦我将它投入生产,我就开始收到 CSRF 错误。一切都是这样运作的。

我已经看到所有的答案都说了从关闭 CSRF 到允许所有事情的一切。我想正确地做到这一点,而不仅仅是关闭一切并打开一切并最终导致安全漏洞。

所以,这就是我所拥有的。

已安装: django-cors-headers django-rest-framework drf-嵌套路由器 ...和其他人

我的 api 在 api.websitename.com 上运行,而 Vue.js 应用在 websitename.com 上运行。

GET 请求效果很好。 OPTION 请求似乎有效。

任何有风险的请求都不起作用。

对于我的 CORS,我在其他 MIDDLEWARE 之前安装了 'corsheaders.middleware.CorsMiddleware',

那么我的 CORS 设置是:

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
    '*.websitename.com',
)

我的 CSRF 设置是:

CSRF_TRUSTED_ORIGINS = [
    "api.websitename.com",
]

无论我如何使用这些,最终都会出现 CSRF 令牌错误。

我已经尝试在我的 Vue App.vue 文件中做这样的事情:

mounted () {
  this.getCSRFToken()
},
methods: {
  getCSRFToken () {
    return axios.get('token/').then(response => {
      axios.defaults.headers.common['x-csrftoken'] = Cookies.get('csrftoken')
    }).catch(error => {
      return Promise.reject(error.response.data)
    })
  }
}

我的想法是,只要 APP 在浏览器中加载,我就会得到一个 CSRF 令牌。但即便如此,当应用尝试执行除 GET 或 OPTION 之外的任何操作时,我仍会收到失败的 CSRF 令牌错误。

这是返回令牌的视图,以防你的古玩:

class CSRFTokenView(APIView):
    permission_classes = (permissions.AllowAny,)

    @method_decorator(ensure_csrf_cookie)
    def get(self, request):
        return HttpResponse()

我知道我可能会在这里混合问题,但欢迎任何可以帮助我解决此问题的建议。

【问题讨论】:

    标签: django vue.js django-rest-framework django-csrf django-cors-headers


    【解决方案1】:

    首先你要使用SessionAuthentication

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.SessionAuthentication',
        )
    }
    

    这将强制执行 CSRF,但匿名用户除外(稍后会详细介绍)。对于浏览器前端,最简单的解决方案是将(浏览器)前端和后端都放在同一个域下 - 这可以让您避免 CORS - 正如上面 cmets 所建议的那样。如果您有其他客户端,则只需使用令牌(DRF 令牌或 JWT) - 但由于存在 XSS 攻击的危险(将令牌存储在 localStorage 中本质上是不安全的),这些对于浏览器的使用并不安全。

    当您使用 axios 时,CSRF 设置非常简单:

    import axios from 'axios'
    
    axios.defaults.xsrfHeaderName = 'X-CSRFToken'
    axios.defaults.xsrfCookieName = 'csrftoken'
    

    因此,您应该使用 CSRF 进行安全会话。几乎。引用上面的链接页面:

    警告:创建登录页面时始终使用 Django 的标准登录视图。这将确保您的登录视图得到适当的保护。

    REST 框架中的 CSRF 验证与标准 Django 的工作方式略有不同,因为需要支持对相同视图的基于会话和非基于会话的身份验证。这意味着只有经过身份验证的请求才需要 CSRF 令牌,并且可以在没有 CSRF 令牌的情况下发送匿名请求。此行为不适用于登录视图,登录视图应始终应用 CSRF 验证。

    这很恶心 - 你要么只使用 Django 服务器端视图,这会使你的 SPA 设计更加复杂,要么在 DRF 中重新创建登录和其他身份验证视图,但需要注意的是使用 @csrf_protect 方法装饰器来强制执行 CSRF关于这些“匿名”的观点。显然,对于使用令牌的客户端,此类视图会中断,因此您可能希望为这些客户端使用不同的端点(可能重新使用相同的基类)。所以您的浏览器登录使用 /auth/browser/login/ 和您的移动登录 /auth/mobile/login/,前者使用 @csrf_protect.

    在学习了contrib auth源代码后,应该仔细从头重新创建登录和其他auth视图;对于普通要求,我会推荐预先存在的解决方案,例如 django-rest-authdjango-all-auth。然而,django-rest-auth 包不是为浏览器前端设计的,并且强制使用令牌生成,而且您需要如上所述包装视图。另一方面,all-auth 为 JS 客户端提供 AJAX 响应,可能是更好的选择。

    【讨论】:

      【解决方案2】:

      到目前为止,解决此问题的最简单方法是为同一域中的所有内容提供服务。您可以让您的 CDN 或代理直接 /api 调用一台服务器,其余的调用到前端服务器。这样就完全不用担心CORS了。

      为了使这项工作正常进行,我认为您只是在 AXIOS 配置中缺少withCredentials = true。 Django 要求发送 CSRF cookie,当 withCredentials 未设置时,不会通过跨源请求发送 cookie。

      axios.interceptors.request.use(function (config) {
        config.withCredentials = true
        return config
      })
      

      另一个可能缺少的设置是 Djano 的 SESSION_COOKIE_DOMAIN。你应该这样设置:

      SESSION_COOKIE_DOMAIN=".mywebsite.com"
      

      第一个点很重要,因为它告诉 Django 和 Web 浏览器使用 *.mywebsite.com 的 cookie,包括 api.mywebsite.com

      如果仍然失败,我建议在 Django 的 CSRF 中间件上设置一个断点,看看缺少什么以使其正常工作。

      【讨论】:

      • 我已经完成了您所说的上述域。问题是,如果我统治世界的邪恶计划成真,API 最终将成为一项服务。所以我需要把它作为一个单独的实例。我正在研究您建议的其他选项。
      • 它仍然可以是一个单独的实例。这取决于您的设置。例如,在 AWS 中,使用 CloudFront 在 S3 上拥有静态文件是很常见的。在这种情况下,您可以让 CloudFront 将一些 URL 指向您的 API 实例,其余的指向 S3。
      • 您还可以在 api.mywebsite.com 和 mywebsite.com/api 中公开 API。
      猜你喜欢
      • 2019-11-11
      • 1970-01-01
      • 2018-01-26
      • 2017-09-19
      • 2023-03-18
      • 1970-01-01
      • 2012-08-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多