ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Aspire + Azure Web PubSub 构建高并发群聊系统

Aspire + Azure Web PubSub 构建高并发群聊系统 1. 项目概述为什么一个“群聊”值得用 Aspire Azure Web PubSub 重做一遍我去年在给一家在线教育平台做实时互动模块时被逼着把 WebSocket 服务从 SignalR 换成 Azure Web PubSub。当时心里是抵触的——不就是发个消息、推个通知SignalR 跑得好好的何必折腾直到上线后第三周我们遭遇了一次突发流量某场免费公开课开课前5分钟3.2万用户同时涌入聊天室SignalR Hub 的 CPU 瞬间飙到98%连接队列堆积新用户连不上老用户消息延迟超12秒。运维同事凌晨三点打电话说“再不切今天早上的课得直播道歉。”那天我通宵研究 Azure Web PubSub 文档第二天就搭出了最小可行原型。不是靠魔法而是靠它彻底解耦了“业务逻辑”和“连接管理”——你的 ASP.NET Core 应用不再需要维护数万个 WebSocket 连接状态也不用操心心跳、重连、断线清理这些脏活累活。Web PubSub 把连接层抽成独立的云服务你只管写业务谁发了什么消息、该转发给谁、要不要存数据库。Aspire 则把这套架构的部署、可观测性、依赖注入全给你兜底了。标题里那个AddAzureWebPubSubHub方法不是个花哨的语法糖它是 Aspire 对 Web PubSub 的“语义封装”它自动注册了 Hub 客户端、配置了连接字符串、绑定了默认的授权策略并把 Hub 实例注入到 DI 容器里——你不用再手写IHttpClientFactory去调用 REST API也不用自己拼接 SAS Token。它背后实际干的是三件事注册IWebPubSubClient用于服务端主动推送注册IWebPubSubServiceClient用于管理连接、分组、权限注册IWebPubSubHubClientT泛型 Hub 客户端类型安全地发消息。这玩意儿适合谁如果你正在用 .NET 做以下任何一种场景它就不是“可选”而是“刚需”在线协作工具白板、文档协同实时交易看板股票、期货、加密货币行情多人游戏状态同步非高帧率动作类比如棋牌、答题、投票IoT 设备状态广播传感器数据汇总推送给监控大屏客服系统坐席状态联动坐席上线/离线/忙碌实时同步给所有管理员。它不适合什么别硬套需要毫秒级响应的高频交易指令Web PubSub 端到端延迟通常在 50–200ms够用但不够极致单机小项目本地开发调试时Web PubSub 的 Azure 依赖会拖慢启动速度不如直接用 MemoryHub强求 P2P 通信的场景Web PubSub 是典型的 Pub/Sub 模型所有消息必须经由服务端中转没有客户端直连能力。我见过太多团队把 WebSocket 当成“高级 HTTP”来用前端用new WebSocket()后端用WebSocketManager手动管理连接池结果一上生产就掉连接、丢消息、内存泄漏。这不是技术不行是没看清 WebSocket 的本质——它不是“更快的 HTTP”而是一种长生命周期、双向、低开销的通道协议。它的难点从来不在“怎么连”而在“连上了之后怎么稳、怎么扩、怎么查”。Aspire Web PubSub 的组合就是把“怎么稳、怎么扩、怎么查”这三座大山直接搬进 Azure 云服务里让你专注写业务代码。2. 架构设计与核心思路拆解为什么放弃 SignalR选择 Web PubSub2.1 信号模型的根本差异从“Hub 中心化”到“Broker 解耦化”SignalR 的设计哲学是“Hub 即中心”。你在Startup.cs或Program.cs里定义一个ChatHub : Hub所有客户端都连到这个 Hub 实例。Hub 里既有OnConnectedAsync这种连接生命周期方法也有SendMessage这种业务方法。好处是开发快、概念直观坏处是——它把连接状态和业务逻辑强耦合在了一起。举个具体例子假设你要实现“仅向同房间用户广播消息”。在 SignalR 里你得这么写public class ChatHub : Hub { private readonly IHubContextChatHub _hubContext; public ChatHub(IHubContextChatHub hubContext) _hubContext hubContext; public async Task SendMessage(string room, string message) { // ❌ 错误示范遍历所有连接手动过滤 var connections await _hubContext.Clients.All.GetConnections(); foreach (var conn in connections.Where(c c.Room room)) { await _hubContext.Clients.Client(conn.ConnectionId).SendAsync(ReceiveMessage, message); } } }这段代码的问题在哪GetConnections()是 SignalR 的私有 API官方不保证兼容性.NET 6 已废弃即使能用它返回的是内存中的连接列表集群部署时多台服务器你只能拿到本机的连接跨节点消息就丢了每次广播都要遍历全部连接O(n) 复杂度3万用户时性能断崖式下跌。而 Web PubSub 的思路是“Broker 即规则引擎”。你不再定义一个“Hub 类”而是定义一套“路由规则”和“事件处理器”。所有客户端连接到 Web PubSub 服务服务根据 URL 路径、查询参数、JWT Token 自动分组。比如你让前端连wss://your-pubsub.webpubsub.azure.com/client/hubs/chat?groupIdroom123userIdabc123Web PubSub 就自动把这个连接加入room123分组并关联userIdabc123元数据。服务端发消息时你只需调用await _hubClient.SendToGroupAsync(room123, new { type message, content message });Web PubSub 服务内部会自动查找room123分组下的所有活跃连接过滤掉已断开的连接按 WebSocket 协议格式打包消息通过最优路径推送给每个客户端。这个过程完全脱离你的应用进程。你的 ASP.NET Core 应用只是个“消息生产者”不保存任何连接状态不参与任何网络 I/O。这就意味着水平扩展无压力加 10 台服务器你的业务代码不用改一行Web PubSub 自动负载均衡故障隔离强哪怕你的 API 服务挂了已建立的 WebSocket 连接依然畅通因为连接在 Web PubSub 服务上可观测性好Azure Portal 里直接看到每秒连接数、消息吞吐量、错误率不用自己埋点。2.2 Aspire 的价值不是“又一个模板”而是“生产就绪的胶水”很多人以为 Aspire 就是个“带 UI 的 Docker Compose”这是巨大误解。Aspire 的核心价值在于它把分布式系统里最麻烦的三件事变成了声明式配置依赖发现与连接字符串注入传统方式下你得在appsettings.json里写死 Web PubSub 的连接字符串或者用 Azure Key Vault 动态加载。Aspire 让你用一行代码声明依赖var builder DistributedApplication.CreateBuilder(args); var pubsub builder.AddAzureWebPubSub(chat-pubsub); // ✅ 自动创建资源生成连接字符串 var api builder.AddProjectProjects.ChatApi(chat-api) .WithReference(pubsub); // ✅ 自动注入连接字符串到环境变量Aspire 后台会自动调用 Azure ARM API 创建 Web PubSub 实例或复用已有实例生成具有Client.Connect权限的 SAS Token把 Token 作为WebPubSubConnectionString环境变量注入到chat-api容器在本地开发时自动启动一个轻量级模拟器azdCLI 支持无需真实 Azure 账号。健康检查与依赖拓扑可视化Aspire Dashboard 不是玩具。当你访问https://localhost:14001能看到chat-api服务是否健康HTTP GET/healthchat-api是否能连上chat-pubsub尝试建立 WebSocket 连接chat-pubsub的当前连接数、消息速率、错误日志摘要。这比你手写一堆HealthCheck和ILogger日志清晰十倍。配置一致性保障开发、测试、生产环境的 Web PubSub 连接字符串经常因手动复制出错。Aspire 用DistributedApplicationManifest一个 JSON 文件统一管理所有配置。你改一处所有环境自动同步。所以AddAzureWebPubSubHub不是孤立的方法它是 Aspire 整个依赖注入体系与 Web PubSub SDK 的深度集成点。它背后绑定的是IWebPubSubHubClientChatHub类型安全的 Hub 客户端IWebPubSubServiceClient用于管理分组、用户、连接IWebPubSubClient用于向特定连接 ID 发送消息IOptionsWebPubSubOptions包含 Hub 名称、连接字符串、重试策略等。这种设计让“写业务”和“管基础设施”彻底分离。你写SendMessage方法时脑子里想的是“我要把消息发给 room123”而不是“我的连接字符串对不对”、“Token 过期了没”、“集群里其他实例能不能收到”。2.3 WebSocket 原生支持为什么不用 SignalR 客户端标题强调“原生 WebSocket”这绝不是为了标新立异。SignalR 客户端无论是 JS 还是 .NET本质上是一个“WebSocket Long Polling Server-Sent Events”的兼容层。它在底层做了大量工作自动降级当 WebSocket 不可用时切换到 SSE 或轮询消息序列化/反序列化JSON 格式带方法名、参数、ID连接恢复断线后自动重连重放未确认消息Hub 方法调用代理hubConnection.invoke(SendMessage, ...)。这些功能很强大但代价是协议不透明你无法直接控制 WebSocket 的subprotocol、headers、ping/pong间隔调试困难浏览器 DevTools 的 Network 标签页里看到的是一堆signalr的二进制帧没法像原生 WebSocket 那样直接看到{type:message,content:hello}移动端兼容性风险某些老旧 Android WebView 对 SignalR 的 SSE 降级支持不完善导致连接失败。而 Web PubSub 强制要求客户端使用标准 WebSocket 协议RFC 6455。这意味着前端可以用最简代码连接const ws new WebSocket(wss://your-pubsub.webpubsub.azure.com/client/hubs/chat?groupIdroom123access_token...); ws.onmessage (event) { const data JSON.parse(event.data); if (data.type message) { console.log(收到消息:, data.content); } };后端发的消息前端收到的就是原始 JSON 字符串零解析成本所有 WebSocket 调试工具如wscat、Postman WebSocket 客户端都能直接连、直接测移动端 AppiOS Swift / Android Kotlin可以用系统原生 WebSocket API不用引入 SignalR SDK。我曾帮一个金融客户做合规审计他们要求所有实时通信协议必须是 IETF 标准不能用任何厂商私有协议。SignalR 被否决而 Web PubSub 因为完全基于 RFC 6455一次过审。3. 核心细节解析与实操要点从零搭建一个可运行的群聊3.1 环境准备与 Aspire 项目初始化先明确前提你不需要 Azure 账号就能开始。Aspire 提供了本地模拟器足够跑通整个流程。所需工具链非常干净.NET SDK 8.0必须Web PubSub SDK 依赖 .NET 8 的System.Net.WebSockets新特性Visual Studio 2022 17.8 或 VS Code C# Dev KitDocker DesktopAspire 默认用容器编排本地模拟器也基于容器Azure CLI可选仅用于真机部署。第一步创建 Aspire 解决方案dotnet new aspire -n ChatApp cd ChatApp这会生成三个项目ChatApp.AppHostAspire 主机定义服务拓扑ChatApp.ApiService你的 ASP.NET Core Web APIChatApp.ServiceDefaults共享的中间件、认证、日志配置。现在把 Web PubSub 加进去。打开AppHost.cs找到var builder DistributedApplication.CreateBuilder(args);这行在它下面添加// ✅ 添加 Web PubSub 服务本地模拟器模式 var pubsub builder.AddAzureWebPubSub(chat-hub) .WithAnnotation(new ContainerImageAnnotation { Registry mcr.microsoft.com, Image azure-webpubsub/azure-webpubsub }) .WithHttpEndpoint(port: 8080, name: http); // 模拟器监听端口 // ✅ 添加 API 服务并引用 PubSub var api builder.AddProjectProjects.ChatApiService(chat-api) .WithReference(pubsub) .WithExternalHttpEndpoints(); // 暴露 API 端口关键点解释.WithAnnotation(...)是告诉 Aspire本地开发时不要去 Azure 创建真实资源而是拉取微软官方镜像mcr.microsoft.com/azure-webpubsub/azure-webpubsub启动一个轻量容器.WithHttpEndpoint(port: 8080)是模拟器的管理端口用于查看连接状态不是 WebSocket 端口WithReference(pubsub)会自动把 Web PubSub 的连接字符串注入到chat-api的环境变量WebPubSubConnectionString中。验证是否成功运行dotnet run --project AppHost.csproj你会看到终端输出类似Building and running application... Starting container webpubsub... Container webpubsub is running on http://localhost:8080 Starting project chat-api... chat-api is ready at https://localhost:7192此时Web PubSub 模拟器已在http://localhost:8080运行API 服务在https://localhost:7192。提示如果遇到docker: command not found请确认 Docker Desktop 已启动并登录。Aspire 模拟器完全依赖 Docker没有替代方案。3.2 API 服务集成AddAzureWebPubSubHub的正确用法打开ChatApiService项目修改Program.cs。在builder.Services配置段添加 Web PubSub Hub 注册// ✅ 正确注册指定 Hub 名称为 chat builder.Services.AddAzureWebPubSubHubChatHub(options { options.ConnectionString builder.Configuration.GetConnectionString(WebPubSub); options.HubName chat; // ⚠️ 必须与前端连接 URL 的 hub 名称一致 }); // ✅ 同时注册服务客户端用于管理分组、用户 builder.Services.AddAzureWebPubSubClient(options { options.ConnectionString builder.Configuration.GetConnectionString(WebPubSub); });这里有两个易错点HubName必须小写且无特殊字符Web PubSub 服务强制要求 Hub 名称符合 DNS 标签规范a-z, 0-9, -且长度 1–63 字符。ChatHub类名可以是 PascalCase但options.HubName必须是chat连接字符串来源builder.Configuration.GetConnectionString(WebPubSub)会自动读取 Aspire 注入的环境变量WebPubSubConnectionString你无需在appsettings.json里手动配置。接着创建ChatHub类。注意它不是继承Hub而是定义一个空类仅作为泛型参数// ✅ ChatHub.cs - 纯标记类无方法 public class ChatHub { }为什么这样设计因为 Web PubSub 的“Hub”概念是服务端的一个逻辑命名空间不是代码里的类。IWebPubSubHubClientChatHub的作用是告诉 SDK“我要操作名为chat的 Hub”。它比字符串chat更类型安全编译期就能检查。现在写一个发送消息的 API Controller[ApiController] [Route(api/[controller])] public class ChatController : ControllerBase { private readonly IWebPubSubHubClientChatHub _hubClient; public ChatController(IWebPubSubHubClientChatHub hubClient) { _hubClient hubClient; } [HttpPost(send)] public async TaskIActionResult SendMessage([FromBody] SendMessageRequest request) { // ✅ 向指定分组发送消息广播 await _hubClient.SendToGroupAsync(request.GroupId, new { type message, sender request.Sender, content request.Content, timestamp DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); return Ok(new { success true }); } } public class SendMessageRequest { public string GroupId { get; set; } default; // 默认分组 public string Sender { get; set; } anonymous; public string Content { get; set; } ; }关键参数说明request.GroupId对应前端连接 URL 中的groupId参数new { ... }匿名对象会被 JSON 序列化后发送Web PubSub 服务不做任何修改原样推送给客户端_hubClient.SendToGroupAsync这是最常用的方法适用于“房间聊天”场景。注意SendToGroupAsync不会返回错误即使分组不存在。Web PubSub 服务会静默丢弃消息。所以务必在前端连接时确保groupId参数正确传递。3.3 前端连接与消息收发纯原生 WebSocket 实现前端代码必须严格匹配 Web PubSub 的连接协议。核心是构造正确的 WebSocket URLwss://your-pubsub-domain/client/hubs/hub-name?groupIdgroup-iduserIduser-idaccess_tokensas-token在本地模拟器模式下URL 是wss://localhost:8080/client/hubs/chat?groupIdroom123userIduser456access_tokenyour-sas-token但access_token怎么获取不能硬编码Web PubSub 要求每次连接都用短期有效的 SAS Token。所以你需要一个 API 接口由后端生成 Token 并返回给前端// 在 ChatController 中添加 [HttpGet(connect-url)] public IActionResult GetConnectUrl([FromQuery] string groupId, [FromQuery] string userId) { // ✅ 使用服务客户端生成 Token var serviceClient _serviceClientFactory.CreateClient(); var token serviceClient.GenerateClientAccessToken( hubName: chat, userId: userId, groupId: groupId, minutesToLive: 60); // Token 有效期 60 分钟 var url $wss://localhost:8080/client/hubs/chat?groupId{Uri.EscapeDataString(groupId)}userId{Uri.EscapeDataString(userId)}access_token{Uri.EscapeDataString(token.Token)}; return Ok(new { url }); }前端 JavaScriptVue 3 Composition API 示例import { ref, onMounted } from vue; export default { setup() { const ws ref(null); const messages ref([]); const inputMsg ref(); const groupId room123; const connect async () { try { // ✅ 第一步获取连接 URL const res await fetch(/api/chat/connect-url?groupId${groupId}userId${Date.now()}); const { url } await res.json(); // ✅ 第二步建立 WebSocket 连接 ws.value new WebSocket(url); ws.value.onopen () { console.log(WebSocket connected); }; ws.value.onmessage (event) { const data JSON.parse(event.data); if (data.type message) { messages.value.push({ sender: data.sender, content: data.content, time: new Date(data.timestamp).toLocaleTimeString() }); } }; ws.value.onerror (error) { console.error(WebSocket error:, error); }; ws.value.onclose () { console.log(WebSocket closed); }; } catch (err) { console.error(Failed to connect:, err); } }; const sendMessage () { if (!ws.value || ws.value.readyState ! WebSocket.OPEN) return; ws.value.send(JSON.stringify({ type: message, content: inputMsg.value, sender: current-user })); inputMsg.value ; }; onMounted(() { connect(); }); return { messages, inputMsg, sendMessage }; } };关键细节ws.value.send(...)发送的是纯文本 JSON 字符串不是 SignalR 那样的二进制帧onmessage回调里event.data就是原始 JSON 字符串直接JSON.parse即可groupId和userId必须和后端GenerateClientAccessToken时传入的一致否则 Token 验证失败连接被拒绝。实操心得我在第一次调试时把groupId写成了room-123带下划线而 Token 里传的是room123结果 WebSocket 连接立刻关闭状态码401。Web PubSub 的错误日志非常友好在 Aspire Dashboard 的webpubsub服务日志里能看到Invalid group id in token的提示。记住前后端groupId、userId、hubName必须完全一致包括大小写和特殊字符。3.4 消息路由与分组管理超越简单广播的实战技巧群聊的核心需求从来不是“发消息”而是“精准投递”。Web PubSub 提供了四层路由能力按优先级从高到低路由层级触发条件适用场景SDK 方法连接 ID指定单个 WebSocket 连接私聊、系统通知如“您的订单已支付”SendToConnectionAsync(connectionId, ...)用户 ID指定userIdToken 中设置用户多设备同步手机、PC 同时收到消息SendToUserAsync(userId, ...)分组 ID指定groupIdToken 中设置房间聊天、频道订阅SendToGroupAsync(groupId, ...)全 Hub不指定任何 ID全局公告、系统广播SendToAllAsync(...)AddAzureWebPubSubHub默认支持SendToGroupAsync但其他方法需要IWebPubSubServiceClient。我们来扩展一个“用户踢出”功能[HttpPost(kick/{userId})] public async TaskIActionResult KickUser(string userId, [FromQuery] string groupId) { var serviceClient _serviceClientFactory.CreateClient(); // ✅ 从分组中移除用户断开其所有连接 await serviceClient.RemoveUserFromGroupAsync(chat, groupId, userId); // ✅ 向该用户发送踢出通知 await serviceClient.SendToUserAsync(chat, userId, new { type kicked, reason You were removed from the group., groupId groupId }); return Ok(); }这里用了两个关键 APIRemoveUserFromGroupAsync立即断开该用户在指定分组的所有连接SendToUserAsync向该用户的所有在线连接不限分组发送消息。这个组合解决了“踢人”场景的终极难题传统方案发消息让前端自己断开但用户可能忽略或伪造连接状态Web PubSub 方案服务端直接操作连接状态100% 可控。另一个实用技巧动态分组。很多群聊需要“按关键词加入房间”比如搜索“#dotnet”就进入 .NET 技术讨论组。你可以用SendToGroupAsync的groupId参数做文章// 前端连接时groupId 传入哈希值 const keyword #dotnet; const groupId btoa(keyword).replace(//g, ); // base64 编码去掉 号 // 结果IzE0Lm5ldA // 后端发送时用相同算法生成 groupId string GetGroupId(string keyword) Convert.ToBase64String(Encoding.UTF8.GetBytes(keyword)).Replace(, );这样不同关键词生成唯一groupIdWeb PubSub 自动隔离流量无需后端维护分组列表。注意事项groupId最大长度 128 字符userId最大 128 字符。避免用长文本直接做 ID建议用 MD5 或 SHA256 哈希后截取前 32 位。4. 实操过程与核心环节实现从本地调试到 Azure 部署4.1 本地调试全流程用 Postman 和 wscat 验证每一步在把代码交给前端之前务必用命令行工具逐层验证。这是避免“前端连不上、后端收不到”这类问题的黄金法则。Step 1验证 Web PubSub 模拟器是否就绪打开浏览器访问http://localhost:8080。你应该看到 Web PubSub 的管理界面显示Hubs: chat和Connections: 0。这是健康标志。Step 2用 wscat 测试 WebSocket 连接安装wscatNode.js 工具npm install -g wscat生成一个临时 Token用 Azure CLI 或在线工具然后连接wscat -c wss://localhost:8080/client/hubs/chat?groupIdtestuserIdtestuseraccess_tokenyour-token-here如果连接成功终端会显示connected (press CTRLC to quit)。此时回到http://localhost:8080页面Connections数应该变成1。Step 3用 Postman 测试 API 发送消息启动 API 服务dotnet run --project ChatApiService.csproj然后用 Postman 发送 POST 请求URL:https://localhost:7192/api/chat/sendBody (JSON):{ groupId: test, sender: server, content: Hello from API! }如果返回200 OK且wscat终端收到了 JSON 消息说明后端到 Web PubSub 的链路通了。Step 4用浏览器 DevTools 直接连接打开 ChromeF12 进入 Console粘贴以下代码const ws new WebSocket(wss://localhost:8080/client/hubs/chat?groupIdtestuserIddevaccess_tokenyour-token); ws.onmessage e console.log(Received:, e.data); ws.onopen () ws.send(JSON.stringify({type:ping}));如果看到Received: {type:ping}恭喜你已经打通了“浏览器 → Web PubSub → API”全链路。实操心得我踩过的最大坑是 SSL。本地模拟器用的是自签名证书Chrome 会拦截wss://localhost:8080。解决方案有两个用ws://localhost:8080非加密仅限本地开发在 Chrome 地址栏输入chrome://flags/#unsafely-treat-insecure-origin-as-secure启用不安全源然后访问http://localhost:8080HTTP 管理界面和ws://localhost:8080WebSocket。生产环境必须用wssAzure Web PubSub 自带免费 TLS 证书无需额外配置。4.2 Azure 真机部署从 Aspire 到 azd 的无缝迁移Aspire 的魔力在于本地代码几乎不用改就能部署到 Azure。核心是azdCLI 工具。Step 1安装 azd 并登录 Azure# 下载 azdhttps://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd azd loginStep 2初始化 azd 环境在ChatApp根目录运行azd init --template aspire这会生成azure.yaml配置文件定义资源部署参数。Step 3修改 azure.yaml指定 Web PubSub 实例名称name: chat-app services: webpubsub: type: azure-webpubsub properties: sku: Free # 或 Standard_S1 hubName: chat api: type: azure-container-app properties: image: mcr.microsoft.com/dotnet/samples:aspire-chat-api env: - name: WebPubSubConnectionString value: ${{ services.webpubsub.connectionString }}Step 4一键部署azd upazd会自动创建 Resource Group部署 Azure Web PubSub 实例构建ChatApiServiceDocker 镜像并推送到 Azure Container Registry创建 Container App并注入 Web PubSub 连接字符串输出公网访问 URL。部署完成后azd show命令会显示所有服务的 URL。你的 API 服务地址类似https://chat-api-xxxx.eastus.azurecontainerapps.ioWeb PubSub 的 WebSocket 地址是wss://chat-hub-xxxx.webpubsub.azure.com/client/hubs/chat。关键配置项说明sku: Free免费层支持最多 100 个并发连接适合测试生产环境选Standard_S11000 连接/秒100万消息/天hubName: chat必须和代码里options.HubName一致value: ${{ services.webpubsub.connectionString }}azd的变量语法自动注入生成的连接字符串。提示首次部署可能耗时 5–10 分钟。azd up会实时输出日志看到Provisioning complete即表示成功。如果失败azd logs可以查看详细错误。4.3 生产环境加固SSL、CORS、认证的必做配置本地跑通只是开始生产环境必须处理三座大山HTTPS、跨域、认证。SSL 配置Azure 自动搞定Azure Web PubSub 默认启用 HTTPS且提供免费的*.webpubsub.azure.com通配符证书。你无需上传证书或配置 TLS 版本。唯一要注意的是前端连接 URL 必须用wss://不能用ws://否则浏览器会阻止。CORS 配置Aspire 时代已过时旧教程常教你配置app.UseCors()但在 Web PubSub 场景下CORS 由 Web PubSub 服务自身控制。你必须在 Azure Portal 的 Web PubSub 实例设置里添加允许的 Origin进入 Azure Portal → 你的 Web PubSub 实例 → Settings → CORS添加https://your-frontend-domain.com支持通配符https://*.example.com保存。如果前端域名不在白名单WebSocket 连接会直接被拒绝状态码403。Aspire 的appsettings.json里配置CORS对 Web PubSub 无效。认证与授权JWT Token 最佳实践Web PubSub 支持两种认证方式SAS Token推荐短期有效最长 24 小时由后端生成前端只负责传递JWT Token高级需自己实现IWebPubSubAuthenticationProvider验证用户身份。SAS Token 已足够安全。关键是要避免 Token 泄露不要在前端存储 Token如 localStorageToken 只在连接时使用连接建立后即失效后端生成 Token 时务必设置userId和groupId利用 Web PubSub 的内置鉴权。例如一个恶意用户即使拿到了 Token也只能连接到指定groupId无法访问其他房间。这就是 Web PubSub 的“最小权限原则”。
返回列表