简单服务器端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语句:
1 // ****** 2 // BLAZOR COOKIE Auth Code (begin) 3 using Microsoft.AspNetCore.Authentication.Cookies; 4 using Microsoft.AspNetCore.Http; 5 using System.Net.Http; 6 // BLAZOR COOKIE Auth Code (end) 7 // ******
将Start 类改为如下,添加注释标记为 BLAZOR COOKIE Auth Code 的部分:
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 public IConfiguration Configuration { get; } 8 // This method gets called by the runtime. Use this method to 9 // add services to the container. 10 // For more information on how to configure your application, 11 // visit https://go.microsoft.com/fwlink/?LinkID=398940 12 public void ConfigureServices(IServiceCollection services) 13 { 14 // ****** 15 // BLAZOR COOKIE Auth Code (begin) 16 services.Configure<CookiePolicyOptions>(options => 17 { 18 options.CheckConsentNeeded = context => true; 19 options.MinimumSameSitePolicy = SameSiteMode.None; 20 }); 21 services.AddAuthentication( 22 CookieAuthenticationDefaults.AuthenticationScheme) 23 .AddCookie(); 24 // BLAZOR COOKIE Auth Code (end) 25 // ****** 26 services.AddRazorPages(); 27 services.AddServerSideBlazor(); 28 services.AddSingleton<WeatherForecastService>(); 29 // ****** 30 // BLAZOR COOKIE Auth Code (begin) 31 // From: https://github.com/aspnet/Blazor/issues/1554 32 // HttpContextAccessor 33 services.AddHttpContextAccessor(); 34 services.AddScoped<HttpContextAccessor>(); 35 services.AddHttpClient(); 36 services.AddScoped<HttpClient>(); 37 // BLAZOR COOKIE Auth Code (end) 38 // ****** 39 } 40 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 41 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 42 { 43 if (env.IsDevelopment()) 44 { 45 app.UseDeveloperExceptionPage(); 46 } 47 else 48 { 49 app.UseExceptionHandler("/Error"); 50 // The default HSTS value is 30 days. 51 // You may want to change this for production scenarios, 52 // see https://aka.ms/aspnetcore-hsts. 53 app.UseHsts(); 54 } 55 app.UseHttpsRedirection(); 56 app.UseStaticFiles(); 57 app.UseRouting(); 58 // ****** 59 // BLAZOR COOKIE Auth Code (begin) 60 app.UseHttpsRedirection(); 61 app.UseStaticFiles(); 62 app.UseCookiePolicy(); 63 app.UseAuthentication(); 64 // BLAZOR COOKIE Auth Code (end) 65 // ****** 66 app.UseEndpoints(endpoints => 67 { 68 endpoints.MapBlazorHub(); 69 endpoints.MapFallbackToPage("/_Host"); 70 }); 71 } 72 }
首先,代码添加了对cookie的支持。 Cookie由应用程序创建,并在用户登录时传递到用户的Web浏览器。Web浏览器将Cookie传递回应用程序以指示用户已通过身份验证。 当用户“注销”时,cookie被删除。
这段代码还添加了:
- HttpContextAccessor
- HttpClient
在代码中使用依赖注入访问的服务。
查看这个链接可以获得关于 httpcontexcessor 如何让我们确定登录用户是谁的完整解释。
添加登录/注销页面
登录(和注销)由.cshtml页面执行。
添加以下Razor页面和代码:
Login.cshtml
1 @page 2 @model BlazorCookieAuth.Server.Pages.LoginModel 3 @{ 4 ViewData["Title"] = "Log in"; 5 } 6 <h2>Login</h2>
Login.cshtml.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Security.Claims; 4 using System.Threading.Tasks; 5 using Microsoft.AspNetCore.Authentication; 6 using Microsoft.AspNetCore.Authentication.Cookies; 7 using Microsoft.AspNetCore.Authorization; 8 using Microsoft.AspNetCore.Mvc; 9 using Microsoft.AspNetCore.Mvc.RazorPages; 10 namespace BlazorCookieAuth.Server.Pages 11 { 12 [AllowAnonymous] 13 public class LoginModel : PageModel 14 { 15 public string ReturnUrl { get; set; } 16 public async Task<IActionResult> 17 OnGetAsync(string paramUsername, string paramPassword) 18 { 19 string returnUrl = Url.Content("~/"); 20 try 21 { 22 // 清除现有的外部Cookie 23 await HttpContext 24 .SignOutAsync( 25 CookieAuthenticationDefaults.AuthenticationScheme); 26 } 27 catch { } 28 // *** !!! 在这里您可以验证用户 !!! *** 29 // 在此示例中,我们仅登录用户(此示例始终登录用户) 30 // 31 var claims = new List<Claim> 32 { 33 new Claim(ClaimTypes.Name, paramUsername), 34 new Claim(ClaimTypes.Role, "Administrator"), 35 }; 36 var claimsIdentity = new ClaimsIdentity( 37 claims, CookieAuthenticationDefaults.AuthenticationScheme); 38 var authProperties = new AuthenticationProperties 39 { 40 IsPersistent = true, 41 RedirectUri = this.Request.Host.Value 42 }; 43 try 44 { 45 await HttpContext.SignInAsync( 46 CookieAuthenticationDefaults.AuthenticationScheme, 47 new ClaimsPrincipal(claimsIdentity), 48 authProperties); 49 } 50 catch (Exception ex) 51 { 52 string error = ex.Message; 53 } 54 return LocalRedirect(returnUrl); 55 } 56 } 57 }
Logout.cshtml
1 @page 2 @model BlazorCookieAuth.Server.Pages.LogoutModel 3 @{ 4 ViewData["Title"] = "Logout"; 5 } 6 <h2>Logout</h2>
Logout.cshtml.cs
1 using System; 2 using System.Threading.Tasks; 3 using Microsoft.AspNetCore.Authentication; 4 using Microsoft.AspNetCore.Authentication.Cookies; 5 using Microsoft.AspNetCore.Mvc; 6 using Microsoft.AspNetCore.Mvc.RazorPages; 7 namespace BlazorCookieAuth.Server.Pages 8 { 9 public class LogoutModel : PageModel 10 { 11 public async Task<IActionResult> OnGetAsync() 12 { 13 // 清除现有的外部Cookie 14 await HttpContext 15 .SignOutAsync( 16 CookieAuthenticationDefaults.AuthenticationScheme); 17 return LocalRedirect(Url.Content("~/")); 18 } 19 } 20 }
添加客户代码
使用以下代码将一个名为 LoginControl.razor 的页面添加到 Shared 文件夹:
1 @page "/loginControl" 2 @using System.Web; 3 <AuthorizeView> 4 <Authorized> 5 <b>Hello, @context.User.Identity.Name!</b> 6 <a class="ml-md-auto btn btn-primary" 7 href="/logout?returnUrl=/" 8 target="_top">Logout</a> 9 </Authorized> 10 <NotAuthorized> 11 <input type="text" 12 placeholder="User Name" 13 @bind="@Username" /> 14 15 <input type="password" 16 placeholder="Password" 17 @bind="@Password" /> 18 <a class="ml-md-auto btn btn-primary" 19 href="/login?paramUsername=@encode(@Username)¶mPassword=@encode(@Password)" 20 target="_top">Login</a> 21 </NotAuthorized> 22 </AuthorizeView> 23 @code { 24 string Username = ""; 25 string Password = ""; 26 private string encode(string param) 27 { 28 return HttpUtility.UrlEncode(param); 29 } 30 }
此代码创建一个登录组件,该组件使用AuthorizeView组件根据用户当前的身份验证包装标记代码。
如果用户已登录,我们将显示其姓名和一个“注销”按钮(可将用户导航到之前创建的注销页面)。
如果未登录,我们会显示用户名和密码框以及一个登录按钮(将用户导航到之前创建的登录页面)。
最后,我们将MainLayout.razor页面(在Shared文件夹中)更改为以下内容:
1 @inherits LayoutComponentBase 2 <div class="sidebar"> 3 <NavMenu /> 4 </div> 5 <div class="main"> 6 <div class="top-row px-4"> 7 <!-- BLAZOR COOKIE Auth Code (begin) --> 8 <LoginControl /> 9 <!-- BLAZOR COOKIE Auth Code (end) --> 10 </div> 11 <div class="content px-4"> 12 @Body 13 </div> 14 </div>
这会将登录组件添加到Blazor应用程序中每个页面的顶部。
打开App.razor页面,并将所有现有代码包含在 CascadingAuthenticationState 标记中。
现在我们可以按F5键运行该应用程序。
我们可以输入用户名和密码,然后单击“登录”按钮…
然后我们可以在 Google Chrome 浏览器 DevTools 中看到 cookie 已经被创建。
当我们单击注销...
Cookie被删除。
调用服务器端控制器方法
此时,所有.razor页面将正确检测用户是否已通过身份验证,并按预期运行。 但是,如果我们向服务器端控制器发出http请求,则将无法正确检测到经过身份验证的用户。
为了演示这一点,我们首先打开startup.cs页面,并将以下代码添加到app.UseEndpoints方法的末尾(在endpoints.MapFallbackToPage(“/ _ Host”)行下),以允许对控制器的http请求 正确路由:
1 // ****** 2 // BLAZOR COOKIE Auth Code (begin) 3 endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); 4 // BLAZOR COOKIE Auth Code (end) 5 // ******
接下来,我们创建一个Controllers文件夹,并使用以下代码添加UserController.cs文件:
1 using Microsoft.AspNetCore.Mvc; 2 namespace BlazorCookieAuth.Controllers 3 { 4 [Route("api/[controller]")] 5 [ApiController] 6 public class UserController : Controller 7 { 8 // /api/User/GetUser 9 [HttpGet("[action]")] 10 public UserModel GetUser() 11 { 12 // Instantiate a UserModel 13 var userModel = new UserModel 14 { 15 UserName = "[]", 16 IsAuthenticated = false 17 }; 18 // Detect if the user is authenticated 19 if (User.Identity.IsAuthenticated) 20 { 21 // Set the username of the authenticated user 22 userModel.UserName = 23 User.Identity.Name; 24 userModel.IsAuthenticated = 25 User.Identity.IsAuthenticated; 26 }; 27 return userModel; 28 } 29 } 30 // Class to hold the UserModel 31 public class UserModel 32 { 33 public string UserName { get; set; } 34 public bool IsAuthenticated { get; set; } 35 } 36 }
我们使用以下代码添加一个新的.razor页面CallServerSide.razor:
1 @page "/CallServerSide" 2 @using BlazorCookieAuth.Controllers 3 @using System.Net.Http 4 @inject HttpClient Http 5 @inject NavigationManager UriHelper 6 @inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor 7 <h3>Call Server Side</h3> 8 <p>Current User: @CurrentUser.UserName</p> 9 <p>IsAuthenticated: @CurrentUser.IsAuthenticated</p> 10 <button class="btn btn-primary" @onclick="GetUser">Get User</button> 11 @code { 12 UserModel CurrentUser = new UserModel(); 13 async Task GetUser() 14 { 15 // Call the server side controller 16 var url = UriHelper.ToAbsoluteUri("/api/User/GetUser"); 17 var result = await Http.GetJsonAsync<UserModel>(url.ToString()); 18 // Update the result 19 CurrentUser.UserName = result.UserName; 20 CurrentUser.IsAuthenticated = result.IsAuthenticated; 21 } 22 }
最后,我们使用以下代码在Shared / NavMenu.razor中添加指向页面的链接:
1 <li class="nav-item px-3"> 2 <NavLink class="nav-link" href="CallServerSide"> 3 <span class="oi oi-list-rich" aria-hidden="true"></span> Call Server Side 4 </NavLink> 5 </li>
我们运行该应用程序并登录。
我们导航到新的Call Server Side控件,然后单击Get User按钮(该按钮将调用刚刚添加的UserController.cs),并且它不会检测到已登录的用户。
要解决此问题,请将CallServerSide.razor页面中的GetUser方法更改为以下内容:
1 async Task GetUser() 2 { 3 // Code courtesy from Oqtane.org (@sbwalker) 4 // We must pass the authentication cookie in server side requests 5 var authToken = 6 HttpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Cookies"]; 7 if (authToken != null) 8 { 9 Http.DefaultRequestHeaders 10 .Add("Cookie", ".AspNetCore.Cookies=" + authToken); 11 // Call the server side controller 12 var url = UriHelper.ToAbsoluteUri("/api/User/GetUser"); 13 var result = await Http.GetJsonAsync<UserModel>(url.ToString()); 14 // Update the result 15 CurrentUser.UserName = result.UserName; 16 CurrentUser.IsAuthenticated = result.IsAuthenticated; 17 } 18 }
我们有一个身份验证cookie,我们只需要在DefaultRequestHeaders中传递它即可。
现在,当我们登录并单击“获取用户”按钮时,控制器方法便能够检测到已登录的用户。