我正在使用 React 前端登录到运行 Express-Session 的 NodeJS 服务器。前端在 localhost:3000 上运行,服务器在 localhost:5000 上运行。
使用来自本地主机的邮递员一切正常(当用户经过正确身份验证并由邮递员接收/存储时,会话 cookie 从服务器发送。后续对服务器上不同路径的邮递员 api 请求使用会话 cookie 并正确检索它应该基于的数据会话内容)。我还可以使用浏览器直接登录服务器(http://localhost:5000/api/authenticate)。服务器生成会话,将 cookie 发送到浏览器,然后浏览器将 cookie 存储在本地。
当我从 React 应用程序中发出 api 请求时,不起作用。服务器正在返回会话 cookie,但浏览器未存储它。经过最近几天的研究(关于这个一般主题有很多问题),这似乎是跨站点请求的问题,但我似乎找不到正确的应用程序和服务器设置集来获取它好好工作。 cookie 是由服务器发送的,但当应用程序发出请求时,浏览器不会存储它。
*** 经过一些额外的故障排除和研究后,我做了一些更新。我的初始 XHR 请求需要预检,请求和响应标头现在看起来是正确的,但浏览器中仍然没有存储 cookie。设置下方有更多详细信息****
服务器设置
var corsOptions = {
origin: 'http://localhost:3000',
credentials: true
};
app.options('*', cors(corsOptions)) // for pre-flight
app.use(cors(corsOptions));
app.use(session({
genid: (req) => {
console.log('Inside the session middleware');
console.log(req.sessionID);
return uuidv4();
},
store: new FileStore(),
secret: 'abc987',
resave: false,
saveUninitialized: true,
cookie: { httpOnly: false, sameSite: 'Lax', hostOnly: false }
}));
app.use( bodyParser.json() );
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, withCredentials, credentials');
next();
});
app.post('/api/authenticate', function(req, res) {
const usernameLower = req.body.username.toLowerCase();
const passwordHash = md5(req.body.password);
connection.query('select USERID from USERS where LOWER(USERNAME)=? && PASSWORD=? ', [usernameLower, passwordHash], function (error, results, fields) {
if (error) {
console.log(error);
req.session.destroy();
res.status(500)
.json({
error: 'Internal error please try again'
});
} else if (results[0]) {
const userId = results[0].USERID;
// setup session data
mySession = req.session;
mySession.user = {};
mySession.user.userId = userId;
res.json(mySession.user);
} else {
console.log('auth failed');
req.session.destroy();
res.status(401)
.json({
error: 'Incorrect email or password'
});
}
});
});
客户端设置——通过单击表单中的提交按钮来触发请求
handleSubmit(event) {
event.preventDefault();
axios.defaults.withCreditials = true;
axios.defaults.credentials = 'include';
axios({
credentials: 'include',
method: 'post',
url: 'http://localhost:5000/api/authenticate/',
headers: {'Content-Type': 'application/json' },
data: {
username: this.state.username,
password: this.state.password
}
})
.then((response) => {
if (response.status === 200) {
this.props.setLoggedIn(true);
console.log('userId: '+response.data.userId);
} else {
console.log("login error");
}
})
.catch(error => console.log(error))
}
下面是发送到浏览器的响应 cookie,但浏览器没有存储它。
{"connect.sid":{"path":"/","samesite":"Lax","value":"s:447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo"}}
来自 XHR 请求的请求标头(飞行前):
OPTIONS /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Referer: http://localhost:3000/
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
飞行前服务器响应标头
HTTP/1.1 204 No Content
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Access-Control-Request-Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: content-type
Content-Length: 0
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive
POST 请求头
POST /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 45
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
服务器响应标头
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Content-Type: application/json; charset=utf-8
Content-Length: 95
ETag: W/"5f-Iu5VYnDYPKfn7WPrRi2d2Q168ds"
Set-Cookie: connect.sid=s%3A447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo; Path=/; SameSite=Lax
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive
我在 https://httptoolkit.tech/will-it-cors/ 使用了“Will it CORS”工具,我的请求/响应标头似乎都是正确的,但仍然没有存储 cookie。
飞行前请求包含正确的出发地 飞行前响应包含正确的允许来源和允许凭证 POST 请求包含正确的来源和允许凭据 POST 响应包含正确的
感谢任何帮助解决这个问题的帮助....
我解决了我的问题,并想发布解决方案,以防其他人遇到这个问题。
回顾一下,后端服务器是使用express的nodejs。以下设置允许前端接受在nodejs服务器上创建的cookie。
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "https://frontendserverdomain.com:3000"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Credentials", true); // allows cookie to be sent
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, HEAD, DELETE"); // you must specify the methods used with credentials. "*" will not work.
next();
});
前端应用基于React,使用axios进行http请求。它托管在“https://frontendserverdomain.com:3000”,该地址被添加到 Nodejs 设置中的“Access-Control-Allow-Origin”标头中(见上文)。
在前端,Axios 需要应用
withCredentials
设置。
axios.defaults.withCredentials = true;
通过这些设置,您的应用程序将能够与后端服务器交换 cookie。
让 CORS 工作的一个问题是确保前端主机正确添加到后端服务器标头“Access-Control-Allow-Origin”。如果访问前端时在 URL 中指定了端口号,则这包括端口号。
就 cookie 交换而言,“Access-Control-Allow-Credentials”和“Access-Control-Allow-Methods”标头必须如上所示正确设置。在“Access-Control-Allow-Methods”上使用通配符将不起作用。
这看起来不对:
axios.defaults.headers.common = {
credentials: "include",
withCredentials: true
}
没有这样的请求标头。相反,凭据是通过 XHR 请求控制的。
使用此选项来确保您的客户端接受 cookie:
axios.defaults.withCredentials = true;
就我而言,cors 配置的基本形式就足够了:允许源站点。
Essential 似乎在发出请求之前在前端 React 应用程序中设置
axios.defaults.withCredentials = true;
。
为了完整起见,这是来自 Laravel 应用程序的示例,在后端具有以下 cors 配置,其中创建了 cookie:
<?php
return [
/*
|--------------------------------------------------------------------------
| (FruitCake) Laravel CORS Options
|--------------------------------------------------------------------------
|
| The allowed_methods and allowed_headers options are case-insensitive.
|
| You don't need to provide both allowed_origins and allowed_origins_patterns.
| If one of the strings passed matches, it is considered a valid origin.
|
| If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers
| all methods / origins / headers are allowed.
|
*/
/*
* You can enable CORS for 1 or multiple paths.
* Example: ['api/*']
*/
'paths' => ['*'],
/*
* Matches the request method. `['*']` allows all methods.
*/
'allowed_methods' => ['*'],
/*
* Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`
*/
'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS'))),
/*
* Patterns that can be used with `preg_match` to match the origin.
*/
'allowed_origins_patterns' => [],
/*
* Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers.
*/
'allowed_headers' => ['*'],
/*
* Sets the Access-Control-Expose-Headers response header with these headers.
*/
'exposed_headers' => [],
/*
* Sets the Access-Control-Max-Age response header when > 0.
*/
'max_age' => 0,
/*
* Sets the Access-Control-Allow-Credentials header.
*/
'supports_credentials' => true,
];