我正在开发一个带有 Node + Express 后端和 NextJS 前端(独立服务器)的全栈应用程序,但在请求浏览器附加作为节点服务器响应标头的一部分发布的 cookie 时遇到问题。设置如下:
使用前端 UI(端口 3001),我可以通过 JsHttp 的 cookie 模块从后端(端口 3000)使用以下代码来出售 cookie:
import { serialize } from 'cookie';
...
const cookie = serialize(TOKEN_NAME, TOKEN_VAL, {
httpOnly: true,
sameSite: 'none',
});
我能够观察到响应中的
Set-Cookie
标头。
但是,在随后的请求中,我没有看到附加cookie。我尝试摆弄上面的 cookie 序列化参数,但没有成功:
以下是我尝试过的论点:
domain: ['.someAlias.com:3000', '.someAlias.com:3001']
path: '/'
domain: '.someAlias.com'
我有一种感觉,这可能只是由于前端和后端服务器端口不同,但所有请求都是在客户端发起的,前往 localhost:3000 (后端端口)。所以不确定我在这里可能做错了什么。
======更新=======
我又进行了一些实验,发现当我直接访问 URL 时,NextJs 会在服务器端渲染页面。当我在应用程序内的页面之间进行转换时,页面会在客户端呈现,并直接查询后端端口 3000。不幸的是,在这两种情况下我都没有看到任何 cookie 被设置......
cookie 必须直接从浏览器发送到服务器,而使用 nextJs 时则不是这样。因为当您访问您的应用程序时,下一个js将在服务器端呈现您的页面,然后请求将从nextjs服务器发送到您的nodejs服务器,因此浏览器会将cookie发送到nextjs服务器而不是您的nodejs服务器。 解决方案是手动从 nextjs 服务器发送 cookie 到 nodejs 。 fetchApi 和 getServerSideProps 函数的示例:
export async function getServerSideProps(context){
try{
const res = await fetch(`your-api-endpoint`, {
method: 'GET',
credentials:'include',
headers: {
'Access-Control-Allow-Credentials': true,
Cookie: context.req.headers.cookie
},
})
const data = await res.json()
if(!res.ok){
throw data
}
}catch(err){
return console.log(err)
}
return {
props: {
data
}
}
}
同一个站点没有
如果您使用
SameSite=none
,您还需要使用 Secure
,这意味着还必须使用 SSL。然后您会说您的 2 个服务器来自不相关的域,这可能不是您想要的,因为浏览器会主动删除 cookie。
本地电脑选项
首先在开发计算机上使用这些设置,并确保浏览器和 Ajax 调用中使用的所有 URL 使用 http://somealias.com:3000 和 http://somealias.com:3001 而不是 localhost。然后 Cookie 将保留在开发计算机上。
部署选项
当您部署到适当的环境时,还要使用 SSL 并设置 cookie 选项
Secure
。最重要的是,两个域必须满足共享相同基域的托管先决条件,例如:
这可确保发出的 cookie 被视为第一方且位于同一站点,因此它们不会被删除。如果不满足先决条件,则您无法在代码中执行任何操作来解决问题。
类似资源
这个 Curity 代码示例 使用本地开发域和相同的站点 cookie 设置,与我上面使用的类似,可能有助于比较。
您必须手动设置Cookies
npm i cookie
import User from "../../models/UserModel.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import cookie from "cookie";
export const UserLoginController = async (req, res) => {
try {
const { email, password } = await req.body;
//checking user
const user = await User.findOne({ email }).select("+password");
if (!user) {
throw new Error("User not Found");
}
console.log(user);
//checking password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) throw new Error("Invalid password");
if (user && isValidPassword) {
const payload = {
userId: user._id,
};
const token = jwt.sign(payload, process.env.SECRET);
const cookies = cookie.serialize("token", token);
return res.status(200).json({ message: "Login successful", cookies });
}
} catch (error) {
return res.status(401).json(error.message);
}
};
npm i react-cookie
您可以阅读有关 react-cookies
的更多信息const LoginSubmitHandler = async (data: any) => {
try {
console.log(data);
const res = await axios.post("http://localhost:5000/users/login", data);
const cookies = res.data.cookies.split("=")[1];
console.log(cookies);
setCookies("token", cookies);
toast.success(res.data.message);
} catch (error: any) {
console.log(error);
toast.error(error.response.data);
}
};
这样您就可以手动设置cookie了
根据我在 GitHub 上找到的这条评论,如果您将
sameSite
设置为 none
,则必须将 secure
设置为 true
;否则,浏览器将永远不会保存cookie。
不幸的是,
secure: true
不适用于 localhost
,因此解决方法是检查您的环境,并在本地工作时将 sameSite
设置为 lax
,将 secure
设置为 false
。
您的代码可能如下所示:
...
const ENVIRONMENT = process.env.NODE_ENV || "development";
const cookie = serialize(TOKEN_NAME, TOKEN_VAL, {
httpOnly: true,
secure: ENVIRONMENT === "production",
sameSite: ENVIRONMENT === "production" ? "none" : "lax",
});
...
您应该使用 res.set Express 方法设置序列化 cookie。
或者,您可以使用 res.cookie 方法,无需额外的
cookie
包,如下所示:
res.cookie(TOKEN_NAME, TOKEN_VAL, {
httpOnly: true,
sameSite: 'none',
});
注意,您不必担心同一域上的不同端口,因为 cookie 不是按端口隔离的,而是仅按域隔离的。无论您使用哪个端口,cookie 都应该是可见的。