让 Django、VUE、CORS 和 CSRF 使用真实世界的例子



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

  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 请求效果很好。 选项请求似乎有效。

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

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

那么我的 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()

我意识到我可能在这里混合了问题,但欢迎任何可以帮助我解决此问题的建议。

首先,您要使用 SessionAuthentication:

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

这将强制执行 CSRF,但匿名用户除外(稍后会详细介绍)。对于浏览器前端,最简单的解决方案是将(浏览器)前端和后端放在同一个域下 - 这样可以避免 CORS - 如上面的评论所建议的那样。如果您有其他客户端,那么只需使用令牌(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 源代码后,应该仔细地从头开始重新创建登录和其他身份验证视图;对于原版需求,我会推荐预先存在的解决方案,如 django-rest-auth 和 django-all-auth。然而,django-rest-auth 包不是为浏览器前端设计的,并且强制使用令牌生成,此外,您需要如上所述包装视图。另一方面,all-auth 为 JS 客户端提供 AJAX 响应,可能是更好的选择。

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

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

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

另一个可能缺少的设置是 贾诺的SESSION_COOKIE_DOMAIN.您应该这样设置它:

SESSION_COOKIE_DOMAIN=".mywebsite.com"

第一个点很重要,因为它告诉 Django,然后是网络浏览器使用 cookie 进行*.mywebsite.com,包括api.mywebsite.com

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

最新更新