比如上圖配置了兩套不同的登錄方案,各有自己的 TenantId?和 ClientId ,要同時支持他們的登錄(其實在同一套?TenantId?和 ClientId 里面配置多個登錄賬戶不就好了,但是......那套登錄的管理是在客戶自己的Azure AD賬戶管理下的,而作為技術支持不想麻煩客戶,更何況客戶不一定同意呢,所以需要第二套專為技術支持提供的用戶組......那么就自己再弄一套吧)
然后問題就來了,在Blazor 頁面要觸發驗證需要調用 HttpContext.ChallengeAsync,你可以試試在.razor 組件內調用?HttpContextAccessor.HttpContext.ChallengeAsync 會發生什么......
當你執行的時候,由于Blazor 使用的是 WebSocket 所以這個 Http 的處理方式就報錯了,說你的請求頭有問題,是不是很無語?
那么怎么解決這個問題呢?在Asp.net Core 3.0 就加入了 EndPoints 終結點的概念,看一下 ChatGPT 是怎么說的
由此看來?EndPoints 可以自定義的控制路由訪問,比Controller更加強大
所以這個時候搞明白一件事情,對于多個 oidc 的登錄,需要自己用一特定路由地址來實現
這個工作都在 StartUp 里完成,只列出核心代碼
1. 注冊兩套 OIDC 登錄方案,注意他們的?CallbackPath? 不能是一樣的
public void ConfigureServices(IServiceCollection services){......services.AddRazorPages();services.AddServerSideBlazor();// Configure authenticationvar authorityFormat = Configuration["AzureAd:Authority"];var callbackPath = Configuration["AzureAd:CallbackPath"];services.AddAuthentication(options =>{options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;}).AddCookie().AddOpenIdConnect(_customerAuthType, options =>{options.Authority = string.Format(CultureInfo.InvariantCulture, authorityFormat, Configuration[$"AzureAd:{_customerAuthType}:TenantId"]);options.ClientId = Configuration[$"AzureAd:{_customerAuthType}:ClientId"];options.CallbackPath = Configuration[$"AzureAd:{_customerAuthType}:CallbackPath"];options.ResponseType = OpenIdConnectResponseType.IdToken; // Use implicit flowoptions.Scope.Add("openid");options.Scope.Add("profile");options.Events = new OpenIdConnectEvents{OnTokenValidated = context =>{var identity = context.Principal.Identity as ClaimsIdentity;identity.AddClaim(new Claim(_authScheme, _customerAuthType));return Task.CompletedTask;}};}).AddOpenIdConnect(_supportAgentAuthType, options =>{options.Authority = string.Format(CultureInfo.InvariantCulture, authorityFormat, Configuration[$"AzureAd:{_supportAgentAuthType}:TenantId"]);options.ClientId = Configuration[$"AzureAd:{_supportAgentAuthType}:ClientId"]; options.CallbackPath = Configuration[$"AzureAd:{_supportAgentAuthType}:CallbackPath"]; options.ResponseType = OpenIdConnectResponseType.IdToken;options.Scope.Add("openid");options.Scope.Add("profile");options.Events = new OpenIdConnectEvents{OnTokenValidated = context =>{var identity = context.Principal.Identity as ClaimsIdentity;identity.AddClaim(new Claim(_authScheme, _supportAgentAuthType));return Task.CompletedTask;}};});services.AddAuthorization();......}
2. 使用?Endpoints 響應自己定義的路由處理 (登錄和登出)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){......app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();endpoints.MapBlazorHub();endpoints.MapFallbackToPage("/_Host");// Add endpoints for login challengesendpoints.MapGet("/login-customer", async context =>{await context.ChallengeAsync(_customerAuthType, new AuthenticationProperties{RedirectUri = "/"});});endpoints.MapGet("/login-support-agent", async context =>{await context.ChallengeAsync(_supportAgentAuthType, new AuthenticationProperties{RedirectUri = "/"});});// Add endpoint for logoutendpoints.MapGet("/logout", async context =>{var user = context.User;if (user.Identity.IsAuthenticated){var authSchemeClaim = user.FindFirst(_authScheme);if (authSchemeClaim != null){var authScheme = authSchemeClaim.Value;var tenant = user.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value;await context.SignOutAsync(authScheme);// sign out from IDPif (tenant != null){// Construct the current full URL var currentUrl = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.PathBase}";context.Response.Redirect($"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout?post_logout_redirect_uri={currentUrl}");}}}await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); });});}
可以看到上面分別注冊了三個路由?/login-customer,/login-support-agent,/logout
一個給客戶登錄用,一個給技術支持登錄用,最后一個是登出,
這個時候再利用 HttpContext 去?Challenge 就不會報錯了,那么 blazor 頁面上所做就是跳轉到上面的路由地址就可以實現相應的登錄和登出了
private void SupportAgentLogin(){navigation.NavigateTo("/login-support-agent", true);}private void Logout(){navigation.NavigateTo("/logout", true);}