8

必须遗漏一些明显的东西,但由于其 CSRF 保护,我非常坚持登录 Django。

查看了使用 cy.getCookie() 来测试使用 HTML Web 表单登录的示例食谱,但如果它推荐的第一件事是禁用 CSRF,那真的没有多大帮助。

Django想要什么:

这是一个正常的、受 CSRF 保护的 Django 登录视图在其传入的 POST 数据中所期望的:

csrfmiddlewaretoken=Y5WscShtwZn3e1eCyahdqPURbfHczLyXfyPRsEOWacdUcGNYUn2EK6pWyicTLSXT
username=guest
password=password
next

它没有在请求标头中查找 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}
    })
})

赛普拉斯的错误:

在此处输入图像描述

我怀疑 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,同样的问题。

4

5 回答 5

10

您可以使用请求获取登录所需的第一个 CSRF 令牌HEAD并查看 cookie(无需解析页面)。

您也可以让您的自定义cy.login()返回令牌(异步,因此您需要使用),而不是如果您之后需要令牌来进行 POST 请求等,则不必再次.then()调用:cy.getCookie('csrftoken')

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')

      })
    })
  })

})

注意:登录后令牌会发生变化,因此需要两次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 }
  })

})
于 2019-05-24T08:10:45.003 回答
3

Cypress 会自动随请求发送名为“csrftoken”的 cookie,但 Django 期望 csrf 令牌称为“csrfmiddlewaretoken”。因此,我必须得到令牌并手动传递它,如下所示:

    cy.getCookie('csrftoken')
    .then((csrftoken) => {
        cy.request({
            method: 'POST',
            url: your_url_here,
            // "form: true" is required here for the submitted information to be accessible via request.POST in Django (even though the docs make it sound like a bare 'POST' request can be made without the "form: true")
            form: true,
            body: {
                csrfmiddlewaretoken: csrftoken.value,
                testing: true,
                obj_model: 'Customer',
                field_name: 'name',
                field_value: 'Customer - Testing'
            }
        })
        .then((result) => {
            expect(result.body.success).to.equal(true)
        })
        .then(() => {
            //additional processing here if needed
        })
    })
于 2018-02-23T19:12:30.177 回答
2

要使用 Cypress 通过 Django 以编程方式登录(即,不使用 UI),最简单的解决方案是更改 Cypress 提供的 CSRF 测试配方中的两个单词。

与https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/logging-in__csrf-tokens/cypress/integration/logging上的 Cypress 配方相比,我在下面所做的两项更改-in-csrf-tokens-spec.js是:

  1. 更改_csrfcsrfmiddlewaretoken; 和
  2. 更改$html.find("input[name=_csrf]").val()$html.find("input[name=csrfmiddlewaretoken]").val()

为 Django 2.2 更新的配方:

// This recipe expands on the previous 'Logging in' examples
// and shows you how to use cy.request when your backend
// validates POSTs against a CSRF token
//
describe('Logging In - CSRF Tokens', function(){
  const username = 'cypress'
  const password = 'password123'

  Cypress.Commands.add('loginByCSRF', (csrfToken) => {
    cy.request({
        method: 'POST',
        url: '/login',
        failOnStatusCode: false, // dont fail so we can make assertions
        form: true, // we are submitting a regular form body
        body: {
          username,
          password,
          csrfmiddlewaretoken: csrfToken // insert this as part of form body
        }
      })
  })

  it('strategy #1: parse token from HTML', function(){
    cy.request('/login')
      .its('body')
      .then((body) => {
        const $html = Cypress.$(body)
        const csrf  = $html.find("input[name=csrfmiddlewaretoken]").val()

        cy.loginByCSRF(csrf)
          .then((resp) => {
            expect(resp.status).to.eq(200)
          })
      })
  })
于 2019-06-27T12:23:26.080 回答
2

根据文档

此外,对于 HTTPS 请求,严格的 referer 检查由 CsrfViewMiddleware 完成。这意味着即使子域可以在您的域上设置或修改 cookie,它也不能强制用户发布到您的应用程序,因为该请求不会来自您自己的确切域。

https://docs.djangoproject.com/en/3.0/ref/csrf/#how-it-works

因此,如果您正在使用 HTTPS 请求测试站点,则可能必须显式设置您的Referer标头。

这为我解决了它:

cy.request({ url: '/accounts/login/', method: 'HEAD' });
cy.getCookie('csrftoken').then(v => {
  cy.request({
    method: 'POST',
    form: true,
    url: '/accounts/login/',
    headers: {
      Referer: `${Cypress.config('baseUrl')}/accounts/login/`
    },
    body: {
      csrfmiddlewaretoken: v.value,
      login: Cypress.env('agentUsername'),
      password: Cypress.env('agentPassword')
    }
  });
});
于 2020-04-28T16:17:10.807 回答
0

你是对的,赛普拉斯没有在正文中发送令牌,因为它是undefined,因为你.get()input获取令牌时使用的方式。

您正在.get()用作同步调用,但它实际上是async。这是因为赛普拉斯将智能地重试查找 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}
    })
})

...

专业提示:使用赛普拉斯的文档搜索通常会为您提供所需的内容 在此处输入图像描述

于 2018-02-19T16:42:05.193 回答