我有一个 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);
});
到目前为止,我所处的位置:
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。
好的,我已经想出了一个解决方案,但也许更聪明的人可以告诉我这是否违反了最佳实践。
由于 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 标头并自己完成一样......