提问者:小点点

浏览器未设置 cookie(set-cookie 在标头中) - Django 后端,Express fetch 前端


我有一个 Django/DRF API 后端,我正在向它发布登录凭据,并期望得到一个“sessionid”cookie 作为回报。它以 https://url.com/api/ 运行。登录endpoint https://url.com/api/api_login/。

我正在使用 ExpressJS 并在前端获取以进行调用。它以 https://url.com/ 运行。登录表单位于 https://url.com/login。

我有一个 Nginx 反向代理将“url.com/api”映射到“url.com:8002”,将“url.com”映射到“url.com:8003”。

以下是后端的简化代码:

# views.py

@method_decorator(csrf_exempt, name='dispatch')
class ApiLogin(APIView):
  def post(self, request):
    form = LoginForm(request.POST)
    if form.is_valid():
      user = authenticate(request, username=form.cleaned_data['username'], password=form.cleaned_data['password'])
      if user is not None:
        auth_login(request, user)

    # at this point, you are either logged in or not
    if request.user.is_authenticated:
      response = HttpResponse(f"Successful login for {form.cleaned_data['username']}.")
      return response
    else:
      response = HttpResponse("Login failed.")
      return response

以下是前端的完整代码:

//*** server.js
const express = require('express');
const app = express();
app.use(express.static('static'));
app.use(express.json());
app.use(express.urlencoded({extended: true}));

const router = require('./router');
app.use(router);



//*** router.js
const express = require('express');
const router = express.Router();
const qs = require('qs');
const fetch = require('node-fetch');

// temporarily running on a self-signed cert, so this bypasses the cert-check
const https = require('https');
const httpsAgent = new https.Agent({
  rejectUnauthorized: false
});

router.get('/login', (req, res) => {
  res.render('login');
});

router.post('/login', (req, res) => {
  fetch('https://url.com/api/api_login/', {
    method: 'POST',
    body: qs.stringify({
      'username': req.body.username,
      'password': req.body.password
    }),
    agent: httpsAgent,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    }
  })
  .then(response => {
    console.log(response.headers);
    res.cookie("test", "value");
    res.render('home');
  })
  .catch(error => {
    console.error(error);
  });

到目前为止,我所处的位置:

  • 我在 Django 服务器端和 Express 客户端都对 CORS 大惊小怪(访问控制允许来源和访问控制允许凭据的各种组合)。我在这里发布的当前代码迭代已将其删除。
  • 我对cookie设置大惊小怪(httpOnly true/false,secure true/false,SameSite Lax/None,path=/,将来过期)
  • 我尝试了 axios 而不是 fetch() 并使用 withCredentials:true
  • 我当前的迭代是带有凭据的 fetch():'include'
  • The Express console.log(response.headers)实际上确实显示了“set-cookie”行:
Headers {
  [Symbol(map)]: [Object: null prototype] {
    server: [ 'nginx/1.21.6' ],
    date: [ 'Fri, 08 Apr 2022 04:48:49 GMT' ],
    'content-type': [ 'text/html; charset=utf-8' ],
    'content-length': [ '29' ],
    connection: [ 'close' ],
    vary: [ 'Accept, Cookie' ],
    allow: [ 'POST, OPTIONS' ],
    'x-frame-options': [ 'DENY' ],
    'x-content-type-options': [ 'nosniff' ],
    'referrer-policy': [ 'same-origin' ],
    'set-cookie': [
      'csrftoken=vNgTkUBruc1xeL27KvBYi9esw12hxK8ohQHWQlur7lmiErddU9FVXRnG0Dxas3v2; expires=Fri, 07 Apr 2023 04:48:49 GMT; Max-Age=31449600; Path=/; SameSite=Lax',
      'sessionid=.eJxVjMsOwiAUBf-FtSEgj1KX7v0Gch8gVUOT0q6M_y5NutDtzJzzFhG2tcStpSVOLC5Ci9MvQ6BnqrvgB9T7LGmu6zKh3BN52CZvM6fX9Wj_Dgq00tcjDAGCs8Yo6w0pjQwqe8g2qY6QOeRw1oq7oqyRPA_OOI0q0AjJiM8X2AE34Q:1ncgYD:vRmuQlX4P82-Utw8qmPzSoS-t6Xo7D89CO0UBtyltVY; expires=Fri, 22 Apr 2022 04:48:49 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax'
    ]
  }
}

这是我的解释,以防它明显表明我做错了什么。我很确定后端很好 - 它提供了设置cookie标头。所以这是一个前端问题,也是一个问题,为什么浏览器不使用该标头并设置 cookie。我确实得到了我手动设置的“测试”cookie,所以我知道这不是因为我的浏览器拒绝cookie。我认为我没有 CORS 问题,因为从服务器和客户端 POV 来看,我位于同一个域 (https://url.com),即使服务器和客户端要转到不同的端口,cookie 也应该与端口无关。但以防万一,我确实尝试为“访问控制-允许-来源:https://url.com:8003”添加 CORS 标头,但这也没有帮助。我没有收到csrf cookie或sessionid cookie。

邮递员也没有得到饼干。如果 cookie 直接命中 https://url.com/api/api_login/ endpoint,Postman 可以获取 cookie。


共1个答案

匿名用户

好的,我已经想出了一个解决方案,但也许更聪明的人可以告诉我这是否违反了最佳实践。

由于 DRF 提供 set-cookie 标头,我在前端使用“set-cookie-parser”来读取标头值并手动设置 cookie:

const express = require('express');
const router = express.Router();
const qs = require('qs');
const fetch = require('node-fetch');
var setCookie = require('set-cookie-parser');

const api_url = "";

const https = require('https');
const httpsAgent = new https.Agent({
  rejectUnauthorized: false
});

router.get('/login', (req, res) => {
  res.render('login');
});

router.post('/login', (req, res) => {
  fetch(api_url + '/api_login/', {
    method: 'POST',
    body: qs.stringify({
      'username': req.body.username,
      'password': req.body.password
    }),
    agent: httpsAgent,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  })
  .then(response => { 
    const cookies = setCookie.parse(response.headers.raw()['set-cookie'], {
      decodeValues: true,
    });
    cookies.forEach(cookie => {
      res.cookie(cookie['name'], cookie['value'], {
        expires: cookie['expires'],
        httpOnly: cookie['httpOnly'],
        maxAge: cookie['maxAge'],
        path: cookie['path'],
        sameSite: cookie['sameSite'],
        secure: cookie['secure'],
      })
    });
    return response.text();
  })
  .then(text => {
    console.log(text);
    res.render('home');
  })
  .catch(error => {
    console.error(error);
  });
});

module.exports = router;

我仍然不完全确定为什么我的浏览器没有设置 cookie 本身。我开始怀疑,虽然我的Express服务器得到了带有“set-cookie”指令的response.header,但它并没有将其传递给我的浏览器。看到我的测试 cookie 是如何正确设置的,我决定只显式地这样做。这是正确的方法吗?是否存在安全隐患?我不知道。因为我也在手动设置所有 cookie 参数(httpOnly 和 Secure),所以我假设它“同样安全”,就好像浏览器使用了 set-cookie 标头并自己完成一样......