如何解决从Azure Function调用需Cookie认证的Blazor应用的认证问题?
方案1:让Azure Function获取Blazor Server的认证Cookie
- 核心思路:通过MSAL获取针对Blazor Server应用的
id_token,调用Blazor Server自定义端点兑换出认证Cookie,再携带Cookie连接SignalR Hub发送通知。 - 具体实现:
- 在Blazor Server的
Program.cs中新增Cookie兑换端点(基于现有认证配置扩展):// 保留原有认证配置 services.AddMicrosoftIdentityWebAppAuthentication(Configuration) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches(); // 添加Cookie兑换API端点 app.MapGet("/api/exchangecookie", async (HttpContext context, IConfiguration config) => { var idToken = context.Request.Query["id_token"].ToString(); if (string.IsNullOrEmpty(idToken)) return Results.BadRequest("缺少id_token"); // 验证id_token有效性 var validator = context.RequestServices.GetRequiredService<ISecurityTokenValidator>(); try { ClaimsPrincipal principal = validator.ValidateToken(idToken, new TokenValidationParameters { ValidAudience = config["AzureAd:ClientId"], ValidIssuer = $"https://login.microsoftonline.com/{config["AzureAd:TenantId"]}/v2.0", ValidateLifetime = true, ValidateIssuerSigningKey = true }, out _); // 生成并返回认证Cookie await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); var authCookie = context.Response.Cookies.FirstOrDefault(c => c.Key.StartsWith(CookieAuthenticationDefaults.CookiePrefix)); return Results.Ok(new { Name = authCookie.Key, Value = authCookie.Value }); } catch { return Results.Unauthorized(); } }).RequireAuthorization(policy => policy.RequireClaim("azp", "<Azure Function的ClientId>")); // 限制仅Function服务主体访问 - Azure Function中获取
id_token、兑换Cookie并连接SignalR:var client = new ConfidentialClientApplicationBuilder("<Function ClientId>", "<Function ClientSecret>") .WithAuthority($"https://login.microsoftonline.com/<TenantId>/v2.0") .Build(); var tokenResult = await client.AcquireTokenForClient(new[] { "<Blazor Server ClientId>/.default" }) .ExecuteAsync(); // 调用兑换Cookie端点 var httpClient = new HttpClient(); var cookieResponse = await httpClient.GetFromJsonAsync<CookieDto>( $"<Blazor Server域名>/api/exchangecookie?id_token={tokenResult.IdToken}"); // 携带Cookie连接SignalR Hub var hubConnection = new HubConnectionBuilder() .WithUrl("<Blazor Server域名>/notificationHub", options => { options.Cookies.Add(new Cookie(cookieResponse.Name, cookieResponse.Value)); }) .Build(); await hubConnection.StartAsync(); await hubConnection.SendAsync("SendNotification", "来自Azure Function的通知");
- 在Blazor Server的
方案2:修改Blazor Server的SignalR Hub支持双重认证(Cookie + Bearer)
- 核心思路:让Blazor Server的SignalR Hub同时接受Cookie认证(适配Blazor客户端)和Bearer令牌认证(适配Azure Function),无需额外兑换Cookie。
- 具体实现:
- 在Blazor Server的
Program.cs中添加Bearer认证支持:// 保留原有Cookie认证配置 services.AddMicrosoftIdentityWebAppAuthentication(Configuration) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches(); // 添加Bearer认证 services.AddAuthentication() .AddJwtBearer(options => { options.Authority = $"https://login.microsoftonline.com/{Configuration["AzureAd:TenantId"]}/v2.0"; options.Audience = Configuration["AzureAd:ClientId"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true }; // 配置SignalR读取URL中的access_token options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/notificationHub")) { context.Token = accessToken; } return Task.CompletedTask; } }; }); // 配置SignalR Hub允许两种认证方式 app.MapHub<NotificationHub>("/notificationHub") .RequireAuthorization(options => { options.AddPolicy("SignalRPolicy", policy => { policy.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme); policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); policy.RequireAuthenticatedUser(); }); }); - Azure Function中获取Bearer令牌并连接SignalR:
var client = new ConfidentialClientApplicationBuilder("<Function ClientId>", "<Function ClientSecret>") .WithAuthority($"https://login.microsoftonline.com/<TenantId>/v2.0") .Build(); var tokenResult = await client.AcquireTokenForClient(new[] { "<Blazor Server ClientId>/.default" }) .ExecuteAsync(); var hubConnection = new HubConnectionBuilder() .WithUrl("<Blazor Server域名>/notificationHub", options => { options.AccessTokenProvider = () => Task.FromResult(tokenResult.AccessToken); }) .Build(); await hubConnection.StartAsync(); await hubConnection.SendAsync("SendNotification", "来自Azure Function的通知");
- 在Blazor Server的
方案3:使用Azure SignalR Service作为中间层(推荐)
- 核心思路:将SignalR的连接管理交给Azure SignalR Service,分别为Blazor Server(Cookie认证)和Azure Function(Bearer认证)配置适配的认证规则,由云服务处理兼容逻辑,无需自定义认证兼容代码。
- 具体实现:
- 在Azure Portal创建Azure SignalR Service,记录连接字符串。
- Blazor Server配置接入Azure SignalR Service,保留原有Cookie认证:
services.AddSignalR() .AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]); // 原有认证配置不变 services.AddMicrosoftIdentityWebAppAuthentication(Configuration) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches(); - Azure Function配置SignalR输出绑定,发送通知:
- 在
local.settings.json中添加Azure SignalR连接字符串和Azure AD配置:{ "Values": { "AzureSignalRConnectionString": "<Azure SignalR连接字符串>", "AzureAd:ClientId": "<Function ClientId>", "AzureAd:TenantId": "<TenantId>", "AzureAd:ClientSecret": "<Function ClientSecret>" } } - 创建发送通知的Function:
[FunctionName("SendNotification")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, [SignalR(HubName = "notificationHub")] IAsyncCollector<SignalRMessage> signalRMessages, ILogger log) { // 可选:验证Function的调用者身份 var authHeader = req.Headers["Authorization"].FirstOrDefault(); if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ")) { var token = authHeader.Substring(7); // 此处添加token验证逻辑 } await signalRMessages.AddAsync(new SignalRMessage { Target = "ReceiveNotification", Arguments = new[] { "来自Azure Function的通知(通过SignalR Service)" } }); return new OkResult(); }
- 在
- Blazor Server的SignalR Hub无需修改,客户端将自动连接到Azure SignalR Service,保持原有Cookie认证流程。
内容的提问来源于stack exchange,提问作者Dkong
相关产品推荐
相关产品推荐

