xasdfg 2020-03-08

为了演示身份验证如何在服务器端 Blazor 应用程序中工作,我们将把身份验证简化为最基本的元素。 我们将简单地设置一个 cookie,然后读取应用程序中的 cookie。
应用程序身份验证

大多数商业 web 应用程序都要求用户登录到应用程序中。

用户输入他们的用户名和密码,对照成员资格数据库进行检查。

一旦通过身份验证,该应用程序即可识别用户,并且现在可以安全地传递内容。
理解了服务器端 Blazor 应用程序的身份验证过程,我们就可以实现一个满足我们需要的身份验证和成员资格管理系统(例如,一个允许用户创建和管理其用户帐户的系统)。
注意:此示例代码不会检查是否有人使用了合法的用户名和密码! 您将需要添加正确的代码进行检查。 这段代码只是对授权用户的过程的演示。
创建应用程序

打开Visual Studio 2019。

创建没有身份验证的 Blazor 服务器应用程序。
添加Nuget软件包

在解决方案资源管理器中,右键单击项目名称并选择 Manage NuGet Packages。

添加对以下库的引用:

另外还有
添加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被删除。
这段代码还添加了:
在代码中使用依赖注入访问的服务。
查看这个链接可以获得关于 httpcontexcessor 如何让我们确定登录用户是谁的完整解释。
添加登录/注销页面

登录(和注销)由.cshtml页面执行。
添加以下Razor页面和代码:
@page
@model BlazorCookieAuth.Server.Pages.LoginModel
@{
ViewData["Title"] = "Log in";
}
<h2>Login</h2>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);
}
}
}@page
@model BlazorCookieAuth.Server.Pages.LogoutModel
@{
ViewData["Title"] = "Logout";
}
<h2>Logout</h2>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中传递它即可。

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