简单服务器端Blazor Cookie身份验证的演示

Posted 林鑫的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单服务器端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         &nbsp;&nbsp;
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)&paramPassword=@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中传递它即可。

现在,当我们登录并单击“获取用户”按钮时,控制器方法便能够检测到已登录的用户。

 

以上是关于简单服务器端Blazor Cookie身份验证的演示的主要内容,如果未能解决你的问题,请参考以下文章

如何从服务器端 Blazor 应用程序中的 Blazor 组件调用 razor 页面而不导致页面刷新

服务器端 Blazor:刷新页面后如何保持用户身份验证?

如何在没有 Microsoft 身份的情况下对 blazor 服务器进行 jwt 身份验证?

如何在 blazor webassembly 项目中对服务器端控制器中的用户进行身份验证?

使用服务器端 Blazor,在连接到 SQL Server 数据库时如何使用 Windows 身份验证模拟用户?

Blazor 使用 Azure AD 身份验证允许匿名访问