简单服务器端Blazor Cookie身份验证的演示
为了演示身份验证如何在服务器端 Blazor 应用程序中工作,我们将把身份验证简化为最基本的元素。 我们将简单地设置一个 cookie,然后读取应用程序中的 cookie。
应用程序身份验证
大多数商业 web 应用程序都要求用户登录到应用程序中。
用户输入他们的用户名和密码,对照成员资格数据库进行检查。
一旦通过身份验证,该应用程序即可识别用户,并且现在可以安全地传递内容。
理解了服务器端 Blazor 应用程序的身份验证过程,我们就可以实现一个满足我们需要的身份验证和成员资格管理系统(例如,一个允许用户创建和管理其用户帐户的系统)。
注意:此示例代码不会检查是否有人使用了合法的用户名和密码! 您将需要添加正确的代码进行检查。 这段代码只是对授权用户的过程的演示。
创建应用程序
打开Visual Studio 2019。
创建没有身份验证的 Blazor 服务器应用程序。
添加Nuget软件包
在解决方案资源管理器中,右键单击项目名称并选择 Manage NuGet Packages。
添加对以下库的引用:
- Microsoft.AspNetCore.Authorization
- Microsoft.AspNetCore.Http
- Microsoft.AspNetCore.Identity
另外还有
- Microsoft.AspNetCore.Blazor.HttpClient
添加Cookie身份验证
打开Startup.cs文件。
在文件顶部添加以下using语句:
// ****** // BLAZOR COOKIE Auth Code (begin) using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using System.Net.Http; // BLAZOR COOKIE Auth Code (end) // ******
将Start 类改为如下,添加注释标记为 BLAZOR COOKIE Auth Code 的部分:
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) { // ****** // BLAZOR COOKIE Auth Code (begin) services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddAuthentication( CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); // BLAZOR COOKIE Auth Code (end) // ****** services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<WeatherForecastService>(); // ****** // BLAZOR COOKIE Auth Code (begin) // From: https://github.com/aspnet/Blazor/issues/1554 // HttpContextAccessor services.AddHttpContextAccessor(); services.AddScoped<HttpContextAccessor>(); services.AddHttpClient(); services.AddScoped<HttpClient>(); // BLAZOR COOKIE Auth Code (end) // ****** } // 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(); // ****** // BLAZOR COOKIE Auth Code (begin) app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseAuthentication(); // BLAZOR COOKIE Auth Code (end) // ****** app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); } }
首先,代码添加了对cookie的支持。 Cookie由应用程序创建,并在用户登录时传递到用户的Web浏览器。Web浏览器将Cookie传递回应用程序以指示用户已通过身份验证。 当用户“注销”时,cookie被删除。
这段代码还添加了:
- HttpContextAccessor
- HttpClient
在代码中使用依赖注入访问的服务。
查看这个链接可以获得关于 httpcontexcessor 如何让我们确定登录用户是谁的完整解释。
添加登录/注销页面
登录(和注销)由.cshtml页面执行。
添加以下Razor页面和代码:
Login.cshtml
@page @model BlazorCookieAuth.Server.Pages.LoginModel @{ ViewData["Title"] = "Log in"; } <h2>Login</h2>
Login.cshtml.cs
using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BlazorCookieAuth.Server.Pages { [AllowAnonymous] public class LoginModel : PageModel { public string ReturnUrl { get; set; } public async Task<IActionResult> OnGetAsync(string paramUsername, string paramPassword) { string returnUrl = Url.Content("~/"); try { // 清除现有的外部Cookie await HttpContext .SignOutAsync( CookieAuthenticationDefaults.AuthenticationScheme); } catch { } // *** !!! 在这里您可以验证用户 !!! *** // 在此示例中,我们仅登录用户(此示例始终登录用户) // var claims = new List<Claim> { new Claim(ClaimTypes.Name, paramUsername), new Claim(ClaimTypes.Role, "Administrator"), }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true, RedirectUri = this.Request.Host.Value }; try { await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); } catch (Exception ex) { string error = ex.Message; } return LocalRedirect(returnUrl); } } }
Logout.cshtml
@page @model BlazorCookieAuth.Server.Pages.LogoutModel @{ ViewData["Title"] = "Logout"; } <h2>Logout</h2>
Logout.cshtml.cs
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BlazorCookieAuth.Server.Pages { public class LogoutModel : PageModel { public async Task<IActionResult> OnGetAsync() { // 清除现有的外部Cookie await HttpContext .SignOutAsync( CookieAuthenticationDefaults.AuthenticationScheme); return LocalRedirect(Url.Content("~/")); } } }
添加客户代码
使用以下代码将一个名为 LoginControl.razor 的页面添加到 Shared 文件夹:
@page "/loginControl" @using System.Web; <AuthorizeView> <Authorized> <b>Hello, @context.User.Identity.Name!</b> <a class="ml-md-auto btn btn-primary" href="/logout?returnUrl=/" target="_top">Logout</a> </Authorized> <NotAuthorized> <input type="text" placeholder="User Name" @bind="@Username" /> <input type="password" placeholder="Password" @bind="@Password" /> <a class="ml-md-auto btn btn-primary" href="/(@Username)&(@Password)" target="_top">Login</a> </NotAuthorized> </AuthorizeView> @code { string Username = ""; string Password = ""; private string encode(string param) { return HttpUtility.UrlEncode(param); } }
此代码创建一个登录组件,该组件使用AuthorizeView组件根据用户当前的身份验证包装标记代码。
如果用户已登录,我们将显示其姓名和一个“注销”按钮(可将用户导航到之前创建的注销页面)。
如果未登录,我们会显示用户名和密码框以及一个登录按钮(将用户导航到之前创建的登录页面)。
最后,我们将MainLayout.razor页面(在Shared文件夹中)更改为以下内容:
@inherits LayoutComponentBase <div class="sidebar"> <NavMenu /> </div> <div class="main"> <div class="top-row px-4"> <!-- BLAZOR COOKIE Auth Code (begin) --> <LoginControl /> <!-- BLAZOR COOKIE Auth Code (end) --> </div> <div class="content px-4"> @Body </div> </div>
这会将登录组件添加到Blazor应用程序中每个页面的顶部。
打开App.razor页面,并将所有现有代码包含在 CascadingAuthenticationState 标记中。
现在我们可以按F5键运行该应用程序。
我们可以输入用户名和密码,然后单击“登录”按钮…
然后我们可以在 Google Chrome 浏览器 DevTools 中看到 cookie 已经被创建。
当我们单击注销...
Cookie被删除。
调用服务器端控制器方法
此时,所有.razor页面将正确检测用户是否已通过身份验证,并按预期运行。 但是,如果我们向服务器端控制器发出http请求,则将无法正确检测到经过身份验证的用户。
为了演示这一点,我们首先打开startup.cs页面,并将以下代码添加到app.UseEndpoints方法的末尾(在endpoints.MapFallbackToPage(“/ _ Host”)行下),以允许对控制器的http请求 正确路由:
// ****** // BLAZOR COOKIE Auth Code (begin) endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // BLAZOR COOKIE Auth Code (end) // ******
接下来,我们创建一个Controllers文件夹,并使用以下代码添加UserController.cs文件:
using Microsoft.AspNetCore.Mvc; namespace BlazorCookieAuth.Controllers { [Route("api/[controller]")] [ApiController] public class UserController : Controller { // /api/User/GetUser [HttpGet("[action]")] public UserModel GetUser() { // Instantiate a UserModel var userModel = new UserModel { UserName = "[]", IsAuthenticated = false }; // Detect if the user is authenticated if (User.Identity.IsAuthenticated) { // Set the username of the authenticated user userModel.UserName = User.Identity.Name; userModel.IsAuthenticated = User.Identity.IsAuthenticated; }; return userModel; } } // Class to hold the UserModel public class UserModel { public string UserName { get; set; } public bool IsAuthenticated { get; set; } } }
我们使用以下代码添加一个新的.razor页面CallServerSide.razor:
@page "/CallServerSide" @using BlazorCookieAuth.Controllers @using System.Net.Http @inject HttpClient Http @inject NavigationManager UriHelper @inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor <h3>Call Server Side</h3> <p>Current User: @CurrentUser.UserName</p> <p>IsAuthenticated: @CurrentUser.IsAuthenticated</p> <button class="btn btn-primary" @onclick="GetUser">Get User</button> @code { UserModel CurrentUser = new UserModel(); async Task GetUser() { // Call the server side controller var url = UriHelper.ToAbsoluteUri("/api/User/GetUser"); var result = await Http.GetJsonAsync<UserModel>(url.ToString()); // Update the result CurrentUser.UserName = result.UserName; CurrentUser.IsAuthenticated = result.IsAuthenticated; } }
最后,我们使用以下代码在Shared / NavMenu.razor中添加指向页面的链接:
<li class="nav-item px-3"> <NavLink class="nav-link" href="CallServerSide"> <span class="oi oi-list-rich" aria-hidden="true"></span> Call Server Side </NavLink> </li>
我们运行该应用程序并登录。
我们导航到新的Call Server Side控件,然后单击Get User按钮(该按钮将调用刚刚添加的UserController.cs),并且它不会检测到已登录的用户。
要解决此问题,请将CallServerSide.razor页面中的GetUser方法更改为以下内容:
async Task GetUser() { // Code courtesy from Oqtane.org (@sbwalker) // We must pass the authentication cookie in server side requests var authToken = HttpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Cookies"]; if (authToken != null) { Http.DefaultRequestHeaders .Add("Cookie", ".AspNetCore.Cookies=" + authToken); // Call the server side controller var url = UriHelper.ToAbsoluteUri("/api/User/GetUser"); var result = await Http.GetJsonAsync<UserModel>(url.ToString()); // Update the result CurrentUser.UserName = result.UserName; CurrentUser.IsAuthenticated = result.IsAuthenticated; } }
我们有一个身份验证cookie,我们只需要在DefaultRequestHeaders中传递它即可。
现在,当我们登录并单击“获取用户”按钮时,控制器方法便能够检测到已登录的用户。