以下是该问题的完整且有效的解决方案:
首先,您需要提供一种身份验证质询请求机制,以支持重定向到身份验证代理,例如 IdentityServer。这仅适用于 HttpContext,它在 SignalR(Blazor 服务器应用程序)中不可用。为了解决这个问题,我们将添加几个 HttpContext 可用的 Razor 页面。更多答案...
创建 Blazor 服务器应用程序。
Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect -Version 3.1.0 或更高版本。
创建一个名为 LoginDisplay (LoginDisplay.razor) 的组件,并将其放置在
共享文件夹。该组件用于MainLayout组件中:
<AuthorizeView>
<Authorized>
<a href="logout">Hello, @context.User.Identity.Name !</a>
<form method="get" action="logout">
<button type="submit" class="nav-link btn btn-link">Log
out</button>
</form>
</Authorized>
<NotAuthorized>
<a href="login?redirectUri=/">Log in</a>
</NotAuthorized>
</AuthorizeView>
将 LoginDisplay 组件添加到 MainLayout 组件中,就在 About 上方
锚元素,像这样
<div class="top-row px-4">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
注意:为了将登录和注销请求重定向到 IdentityServer,我们必须创建两个 Razor 页面,如下所示:
- 创建一个 Login Razor 页面 Login.cshtml (Login.cshtml.cs) 并将它们放在 Pages 文件夹中,如下所示:
登录.cshtml.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
await HttpContext.ChallengeAsync("oidc", new
AuthenticationProperties { RedirectUri = redirectUri } );
}
}
此代码启动您在 Startup 类中定义的 Open Id Connect 身份验证方案的质询。
- 创建一个 Logout Razor 页面 Logout.cshtml (Logout.cshtml.cs) 并将它们也放在 Pages 文件夹中:
注销.cshtml.cs
using Microsoft.AspNetCore.Authentication;
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnGetAsync()
{
await HttpContext.SignOutAsync();
return Redirect("/");
}
}
此代码将您注销,将您重定向到 Blazor 应用的主页。
将 App.razor 中的代码替换为以下代码:
@inject NavigationManager NavigationManager
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@{
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
NavigationManager.NavigateTo($"login?redirectUri={returnUrl}", forceLoad: true);
}
</NotAuthorized>
<Authorizing>
Wait...
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
将 Startup 类中的代码替换为以下内容:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using System.Net.Http;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Logging;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddAuthorizationCore();
services.AddSingleton<WeatherForecastService>();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme =
OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://demo.identityserver.io/";
options.ClientId = "interactive.confidential.short";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.UseTokenLifetime = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.TokenValidationParameters = new
TokenValidationParameters
{
NameClaimType = "name"
};
options.Events = new OpenIdConnectEvents
{
OnAccessDenied = context =>
{
context.HandleResponse();
context.Response.Redirect("/");
return Task.CompletedTask;
}
};
});
}
// This method gets called by the runtime. Use this method to configure
the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
重要提示:在上面的所有代码示例中,您必须根据需要添加 using 语句。其中大部分是默认提供的。此处提供的使用是启用身份验证和授权流程所必需的。
- 运行您的应用程序,单击登录按钮进行身份验证。您将被重定向到允许您执行 OIDC 登录的 IdentityServer 测试服务器。您可以输入用户名:bob 和密码bob,点击确定按钮后,您将被重定向到您的主页。另请注意,您可以使用外部登录提供商 Google(尝试一下)。请注意,在您使用身份服务器登录后,LoginDisplay 组件会显示字符串
"Hello, <your user name>"。
注意:当你在试验你的应用时,你应该清除浏览数据,如果你想被重定向到身份服务器的登录页面,否则你的浏览器可能会使用缓存的数据。请记住,这是一种基于 cookie 的授权机制...
请注意,在这里创建登录机制不会使您的应用程序比以前更安全。任何用户都无需登录即可访问您的网络资源。为了保护您网站的某些部分,您还必须实施授权,通常,经过身份验证的用户有权访问受保护的资源,除非实施其他措施,例如角色、策略等。以下是演示如何您可以保护您的 Fetchdata 页面免受未经授权的用户的攻击(同样,经过身份验证的用户被视为有权访问 Fetchdata 页面)。
在 Fetchdata 组件页面的顶部,为 Authorize 属性添加 @attribute 指令,如下所示:@attribute [Authorize]
当未经身份验证的用户尝试访问 Fetchdata 页面时,会执行 AuthorizeRouteView.NotAuthorized 委托属性,因此我们可以添加一些代码将用户重定向到同一身份服务器的登录页面进行身份验证。
NotAuthorized 元素中的代码如下所示:
<NotAuthorized>
@{
var returnUrl =
NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
NavigationManager.NavigateTo($"login?redirectUri=
{returnUrl}", forceLoad: true);
}
</NotAuthorized>
这会检索您尝试访问的最后一个页面的 url,即 Fetchdata 页面,然后导航到执行密码质询的 Login Razor 页面,即,用户被重定向到身份服务器的登录页面进行身份验证。
用户通过身份验证后,他被重定向到 Fetchdata 页面。