51 lines
1.7 KiB
Markdown
51 lines
1.7 KiB
Markdown
---
|
|
title: Avoid Shared Module State for Request Data
|
|
impact: HIGH
|
|
impactDescription: 防止并发 bug 和请求数据泄漏
|
|
tags: server, rsc, ssr, concurrency, security, state
|
|
---
|
|
|
|
## 避免为请求数据使用共享模块状态
|
|
|
|
对于 React Server Components 以及在 SSR 期间渲染的客户端组件,应避免使用可变的模块级变量来共享请求范围内的数据。服务端渲染可以在同一进程中并发执行。如果一个渲染向共享模块状态写入数据,而另一个渲染读取该状态,就可能出现竞态条件、跨请求污染以及安全漏洞,导致一个用户的数据出现在另一个用户的响应中。
|
|
|
|
请将服务端的模块作用域视为进程级的共享内存,而非请求局部状态。
|
|
|
|
**错误做法(请求数据在并发渲染间泄漏):**
|
|
|
|
```tsx
|
|
let currentUser: User | null = null
|
|
|
|
export default async function Page() {
|
|
currentUser = await auth()
|
|
return <Dashboard />
|
|
}
|
|
|
|
async function Dashboard() {
|
|
return <div>{currentUser?.name}</div>
|
|
}
|
|
```
|
|
|
|
如果两个请求重叠,请求 A 可以设置 `currentUser`,然后请求 B 在请求 A 完成渲染 `Dashboard` 之前将其覆盖。
|
|
|
|
**正确做法(将请求数据保持在渲染树局部):**
|
|
|
|
```tsx
|
|
export default async function Page() {
|
|
const user = await auth()
|
|
return <Dashboard user={user} />
|
|
}
|
|
|
|
function Dashboard({ user }: { user: User | null }) {
|
|
return <div>{user?.name}</div>
|
|
}
|
|
```
|
|
|
|
安全例外情况:
|
|
|
|
- 在模块作用域一次性加载的不可变静态资源或配置
|
|
- 有意设计为跨请求复用且键值正确的共享缓存
|
|
- 不存储请求或用户特定可变数据的进程级单例
|
|
|
|
关于静态资源和配置,请参见 [Hoist Static I/O to Module Level](./server-hoist-static-io.md)。
|