肯定遗漏了一些明显的东西,但由于它的 CSRF 保护,我非常坚持登录到 Django。
我查看了使用 cy.getCookie() 来测试使用 HTML Web 表单登录的示例食谱,但如果它推荐的第一件事是禁用 CSRF,那确实没有多大帮助。
这是正常的、受 CSRF 保护的 Django 登录视图在其传入的 POST 数据中所期望的:
csrfmiddlewaretoken=Y5WscShtwZn3e1eCyahdqPURbfHczLyXfyPRsEOWacdUcGNYUn2EK6pWyicTLSXT
username=guest
password=password
next
Run Code Online (Sandbox Code Playgroud)
它不是在请求头中寻找 CSRF,也不x-csrf-token是在响应头上设置。
而且,使用我的代码,我从不传入让 Django 返回 403 错误的 csrf 令牌。
Cypress.Commands.add("login", (username, password) => {
var login_url = Cypress.env("login_url");
cy.visit(login_url)
var hidden_token = cy.get("input[name='csrfmiddlewaretoken']").value;
console.log(`hidden_token:${hidden_token}:`)
console.log(`visited:${login_url}`)
var cookie = cy.getCookie('csrftoken');
// debugger;
var csrftoken = cy.getCookie('csrftoken').value;
console.log(`csrftoken:${csrftoken}:`)
console.log(`request.POST`)
cy.request({
method: 'POST',
form: true,
url: login_url,
// body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': cy.getCookie('csrftoken').value}
body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': hidden_token}
})
})
Run Code Online (Sandbox Code Playgroud)
我怀疑 POST 数据与undefined通过隐藏表单输入或 cookie 获取方法获得的令牌有关,如console.logfor所示。
现在,我已经开始查看https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/logging-in__csrf-tokens,我认为我应该能够调整策略 #1 :从 HTML 解析令牌以进行提取,$("input[name='csrfmiddlewaretoken']").value但我希望之前有人这样做过。
我的另一个想法是有条件地向 Django 添加一个请求中间件,该中间件将从请求标头中获取 csrftoken 并在丢失时注入 POST 表单数据。如果我在CSRF内容之前插入它以触发,那会起作用吗?
最后,我计划将sessionid令牌排除在重置之外,以便我可以在登录一次后运行多个测试。
env:Django 1.10,cypress 1.4.2,现在升级到 2.0.0,同样的问题。
您可以使用HEAD请求获取登录所需的第一个 CSRF 令牌并查看 cookie(无需解析页面)。
此外,您可以让您的自定义cy.login()返回令牌(异步,因此您需要使用.then()),而不必cy.getCookie('csrftoken')再次调用,如果您之后需要为 POST 请求提供令牌等:
Cypress.Commands.add('login', (username, password) => {
return cy.request({
url: '/login/',
method: 'HEAD' // cookies are in the HTTP headers, so HEAD suffices
}).then(() => {
cy.getCookie('sessionid').should('not.exist')
cy.getCookie('csrftoken').its('value').then((token) => {
let oldToken = token
cy.request({
url: '/login/',
method: 'POST',
form: true,
followRedirect: false, // no need to retrieve the page after login
body: {
username: username,
password: password,
csrfmiddlewaretoken: token
}
}).then(() => {
cy.getCookie('sessionid').should('exist')
return cy.getCookie('csrftoken').its('value')
})
})
})
})
Run Code Online (Sandbox Code Playgroud)
注意:登录后令牌会发生变化,因此cy.getCookie('csrftoken')需要调用两次。
之后,您可以在测试中按以下方式使用它(请参阅https://docs.djangoproject.com/en/dev/ref/csrf/了解为什么需要标题):
cy.login().then((csrfToken) => {
cy.request({
method: 'POST',
url: '/api/baz/',
body: { 'foo': 'bar' },
headers: { 'X-CSRFToken': csrfToken }
})
})
Run Code Online (Sandbox Code Playgroud)
你是对的,赛普拉斯没有在正文中发送令牌,因为它是,因为你在 上undefined使用的方式来获取令牌。.get()input
您正在使用.get()同步调用,但它实际上是async。这是因为 Cypress 会智能地重试查找 DOM 元素,而这需要不确定的时间。这是赛普拉斯支持内置测试的核心概念。Cypress 文档比我更详细地介绍了这一点,因此请在此处查看: https: //docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Default-Assertions
在您的情况下,如何访问 DOM 中元素的属性应该放在回调中:
cy.get("input[name='csrfmiddlewaretoken']").then($input=>{
const hidden_token = $input.val()
cy.request({
method: 'POST',
form: true,
url: login_url,
// body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': cy.getCookie('csrftoken').value}
body: {'username': 'guest', 'password': 'password', 'csrfmiddlewaretoken': hidden_token}
})
})
Run Code Online (Sandbox Code Playgroud)
...