比如上图配置了两套不同的登录方案,各有自己的 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 authentication
var 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 flow
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, _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 challenges
endpoints.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 logout
endpoints.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 IDP
if (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);
}