ASP.NET JWT 与 Next.js SSR 如何设计多客户端架构?
建议从共享 API 加 Web BFF 开始
对于需要登录的 Next.js SSR 应用,可以优先考虑由 ASP.NET 提供共享业务 API,Next.js 负责 Web BFF。未来的移动和桌面客户端可以根据需要直接调用 API。
BFF 不一定要作为新服务单独部署。它可以是 Next.js 中处理会话、服务端 API 调用和页面数据适配的一组服务端模块。客户端数量增加后,也不必马上为每一端新增 BFF。
如果系统以公开内容展示为主,登录功能很少,Next.js 服务端直接调用 API 就可能足够。是否引入完整的会话管理和数据聚合层,应根据实际需求决定。
如何划分组件职责
可以采用以下结构:
浏览器 ──会话 Cookie──> Next.js / Web BFF
│
└──Access Token──> ASP.NET API
移动客户端 ──Access Token────────────────────> ASP.NET API
桌面客户端 ──Access Token────────────────────> ASP.NET API
各组件可以按以下方式分工:
- ASP.NET API:处理业务规则、资源访问授权、数据持久化和 Access Token 验证。
- Next.js / Web BFF:处理浏览器会话、SSR 数据获取,以及 Web 页面需要的接口聚合和响应适配。
- 身份服务:处理用户认证和令牌签发。是否单独建设或接入现有服务,需要结合当前登录实现决定。
业务权限检查应保留在 ASP.NET API 中。例如,判断用户是否有权读取某张订单,不能只依赖 BFF,因为移动和桌面客户端也会访问同一资源。
Web 与 SSR 如何处理认证
采用 BFF 的 Web 应用可以让浏览器持有会话 Cookie,由服务端保存调用 API 所需的 Access Token,以及存在时的 Refresh Token。
典型流程如下:
- 用户完成登录。
- Next.js 服务端建立会话,并保存与会话关联的令牌。
- 浏览器后续通过 Cookie 访问 Next.js。
- Next.js 根据会话取得 Access Token,再调用 ASP.NET API。
- ASP.NET 验证令牌并执行资源级权限检查。
这样,SSR 可以从当前请求中识别用户并获取数据,浏览器 JavaScript 也不需要读取 API 令牌。
下面仅展示请求关系,路径和占位符不代表框架内置接口:
GET /account HTTP/1.1
Host: web.example.com
Cookie: __Host-session=<opaque-session-id>
Next.js 在服务端查找会话后,向 API 发起请求:
GET /api/account HTTP/1.1
Host: api.example.com
Authorization: Bearer <access-token>
生产环境中的会话 Cookie 可以采用以下属性:
Set-Cookie: __Host-session=<opaque-session-id>; Path=/; Secure; HttpOnly; SameSite=Lax
使用 __Host- 前缀时,需要 HTTPS、Path=/,且不能设置 Domain。选择 SameSite 属性时,还要兼顾登录回调等跨站流程。
HttpOnly 能阻止脚本直接读取 Cookie,但不能阻止恶意脚本代表用户发起请求。修改数据的接口仍需配置 CSRF 防护,例如校验请求来源并使用 CSRF Token。不要用 GET 执行状态变更。
SSR 不需要绕回自己的 HTTP 接口
Next.js 渲染页面时,可以直接调用服务端数据访问函数,由该函数读取会话并请求 ASP.NET API,通常没有必要再请求自己的 BFF HTTP 路由。
浏览器需要异步加载数据或提交操作时,可以调用对应的 BFF 接口。两条路径可以复用同一套服务端逻辑。
对于用户专属数据,需要检查页面、数据请求和 CDN 各层的缓存策略,避免不同用户共享响应。具体配置应以所用 Next.js 版本及部署方式为准。
移动和桌面客户端如何认证
JWT 是一种令牌格式,不能单独定义完整的登录流程。现有后端能够验证 JWT,也不代表它已经提供 OAuth 2.0 或 OpenID Connect。
如果采用支持 OAuth 2.0 / OpenID Connect 的身份服务,移动应用和多数原生桌面应用通常可以使用 Authorization Code Flow with PKCE,通过系统浏览器完成登录。
这些客户端通常属于 public client,不能把随安装包分发的固定 client_secret 当作可靠秘密。令牌应存放在平台提供的安全存储中。是否使用 Refresh Token,以及如何轮换和撤销,应由身份服务的能力和安全策略决定。
如果目前只有自定义的用户名密码登录接口,需要先确认令牌签发、续期、撤销及客户端注册能力,再决定如何演进。增加 BFF 不会自动补齐这些认证能力。
不同客户端可以使用同一用户身份体系,但应拥有各自的会话和令牌。不要让 Web、移动和桌面应用复制共用一份 Refresh Token。
直接调用 API 与多个 BFF 如何取舍
共享业务 API 和 BFF 可以同时存在。BFF 通常位于共享 API 之前,负责客户端适配。
| 方案 | 适用条件 | 主要代价 |
|---|---|---|
| 各客户端直接调用共享 API | 接口已经适合多端使用,数据聚合较少 | 各端分别处理认证状态和请求编排 |
| 共享 API 加 Web BFF | Web 需要 SSR、Cookie 会话或页面数据聚合 | 增加服务端会话管理和故障排查环节 |
| 共享 API 加多个 BFF | 各端的数据形态、发布节奏或团队职责明显不同 | 增加部署、监控和维护成本,容易重复实现逻辑 |
浏览器直接调用 ASP.NET API 也是可行方案,但需要设计浏览器端的令牌存储、续期,以及跨域时的 CORS。若令牌只存于浏览器存储中,首次 SSR 请求无法直接读取它,还需要额外的认证衔接设计。
移动和桌面端可以先直接调用共享 API。如果它们频繁需要聚合多个接口、减少网络往返或使用明显不同的数据结构,再考虑增加对应 BFF。
落地时需要处理的细节
- 明确身份体系。确认谁负责登录、签发令牌、刷新令牌和退出登录,避免 Next.js 与 ASP.NET 各自形成不一致的认证状态。
- 集中管理 Web 会话。Next.js 部署多个实例时,应使用共享会话存储或其他能够跨实例工作的会话方案,不能只依赖单个进程的内存。
- 在 API 验证令牌和权限。验证内容包括签名、签发方、受众及有效期等,并按接口检查访问权限。客户端传来的用户 ID 不能作为可信身份依据。
- 限制 BFF 的转发能力。使用明确的上游地址和接口映射,设置超时,避免把 BFF 做成接受任意目标 URL 的代理。
- 定义刷新与退出行为。同一会话存在并发请求时,应协调令牌刷新。退出登录时销毁本地会话,并在支持的情况下撤销 Refresh Token。已签发的 JWT 是否立即失效,取决于 API 是否具有额外的撤销检查机制。
备注:内容仅供参考。
