我正在构建一个非常简单的博客应用程序。没有认证。每个用户都通过其 IP 地址(存储在数据库中)进行识别,以提供个性化体验,例如为帖子添加书签、评论、点赞等...
在我的根布局中,我调用 DB 来获取与用户相关的数据。请注意,当用户登陆页面时,它会立即运行。
export default async function RootLayout(props: React.PropsWithChildren) {
const user = await getUser()
const store = { user }
...
}
该函数进行数据库调用。不过,在调用之前,我们需要从
headers.get('x-forwarded-for')
获取用户IP地址。
export default async function getUser() {
const { id: userId } = getUserId()
...
}
这只是从标头中提取 IP 地址。
export function getUserId() {
const headers = NextHeaders()
const fallback = '171.23.136.120' // my Ip address
// vercel exposes the ip
let ip = headers.get('x-forwarded-for')
return {
id: ip ?? fallback,
}
}
当用户登陆网站时。应用程序尝试获取 SSR 中有关用户的所有信息(如果数据库中存在),例如书签、点赞、评论等列表...并将其呈现在 UI 中。
但是,从标头返回的 IP 地址与用户进行 API 调用时不同。例如单击按钮即可为帖子添加书签。
我很困惑为什么会出现这种情况,以及如何解决这个问题。
谢谢
首先,必须问一个问题:您是否排除了记录来自两个完全不同的用户的交互的可能性?
X-Forwarded-For
标头比您想象的要复杂得多。
Forwarded
标头取代。就您而言,这可能不是问题,因为您知道您已部署在 Vercel 上并且您确实在此标头中获得了值。NextHeaders
对象如何处理这个问题?它是否将它们全部组合成一个字符串数组?它会默默地用后面的条目覆盖前面的条目吗?client, proxy1, proxy2...
你在处理这个吗?还有更多的考虑因素,特别是如果您将其用于任何类型的安全性。 MDN 上的整个页面都值得一读:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
制作这个单一列表有两种方法:
- 用逗号连接 X-Forwarded-For 完整标头值,然后用逗号将其拆分为列表,或者
- 用逗号将每个 X-Forwarded-For 标头拆分为列表,然后加入列表
如果您真的关心正确执行此操作,MDN 页面提供了两个选项:
选择第一个可信的X-Forwarded-For客户端IP地址时,需要进行额外的配置。常用的方法有两种:
可信代理数量:互联网之间的反向代理数量 并且服务器已配置。搜索 X-Forwarded-For IP 列表 从最右边算起减一。 (例如,如果有 只有一个反向代理,该代理将添加客户端的 IP 地址, 所以应该使用最右边的地址。如果有三个反向 代理,最后两个 IP 地址将是内部 IP 地址。)
可信代理 list:受信任反向代理的 IP 或 IP 范围是 配置。 X-Forwarded-For IP 列表是从 最右边,跳过受信任代理列表上的所有地址。 第一个不匹配的地址是目标地址。
但大多数人只是选择最左边(第一个)值,在大多数情况下应该是客户端IP。
您可能想要使用第 3 方库(例如,https://www.npmjs.com/package/request-ip),它应该为您处理所有这些情况(除非它无法选择一个适当的值得信赖的 IP,并且只会使用第一个值)。
但是,我不得不说,使用客户端IP来识别用户并不是一个好主意。一所大学可能在同一公共 IP 上拥有数千名用户。
tl;dr 不确定为什么您会遇到这种情况,但可能是测量错误(即,这是两个不同的用户)或 X-Forwarded-For 标头处理不当。
附注也许应该阻止这些 IP 地址。它可能是你的,你不会想对自己进行人肉搜索。