《进击吧!Blazor!》是自己与张善友老师单干的Blazor零根底入门教程视频,此教程能让一个从未接触过Blazor的程序员把握开发Blazor利用的能力。
视频地址:https://space.bilibili.com/48...
Blazor WebAssembly 是单页利用 (SPA) 框架,用于应用 .NET 生成交互式客户端 Web 利用,采纳 C# 代替 JavaScript 来编写前端代码
本系列文章因篇幅无限,省略了局部代码,残缺示例代码:https://github.com/TimChen44/...
作者:陈超超
Ant Design Blazor 我的项目贡献者,领有十多年从业教训,长期基于.Net 技术栈进行架构与开发产品的工作,现就职于正泰团体。
邮箱:timchen@live.com
欢送各位读者有任何问题分割我,咱们共同进步。

我的的 ToDo 利用基本功能曾经实现,然而本人的待办当然只有本人晓得,所以咱们这次给咱们的利用减少一些平安方面的性能。

Blazor 身份验证与受权

身份验证

Blazor Server 利用和 Blazor WebAssembly 利用的平安计划有所不同。

  • Blazor WebAssembly

Blazor WebAssembly 利用在客户端上运行。 因为用户可绕过客户端查看,因为用户可批改所有客户端代码, 因而受权仅用于确定要显示的 UI 选项,所有客户端应用程序技术都是如此。

  • Blazor Server

Blazor Server 利用通过应用 SignalR 创立的实时连贯运行。 建设连贯后,将解决基于 SignalR 的利用的身份验证。 可基于 cookie 或一些其余持有者令牌进行身份验证。

受权

AuthorizeView 组件依据用户是否取得受权来选择性地显示 UI 内容。 如果只须要为用户显示数据,而不须要在过程逻辑中应用用户的标识,那么此办法很有用。

<AuthorizeView>  <Authorized>    <!--验证通过显示-->  </Authorized>  <NotAuthorized>    <!--验证不通过显示-->  </NotAuthorized></AuthorizeView>

Blazor 中应用 Token

在 Blazor WebAssembly 模式下, 因为利用都在客户端运行,所以应用 Token 作为身份认证的形式是一个比拟好的抉择。
根本的应用时序图如下

对于平安要求不高的利用采纳这个办法简略、易保护,齐全没有问题。

然而 Token 自身在安全性上存在以下两个危险:

  1. Token 无奈登记,所以能够在 Token 有效期内发送的非法申请,服务端无能为力。
  2. Token 通过 AES 加密存储在客户端,实践上能够进行离线破解,破解后就能任意伪造 Token。

因而遇到平安要求十分高的利用时,咱们须要认证服务进行 Token 的有效性验证

革新 ToDo

接着咱们对之前的 ToDo 我的项目进行革新,让他反对登录性能。

ToDo.Shared

先把前后端交互所需的 Dto 创立了

public class LoginDto{    public string UserName { get; set; }    public string Password { get; set; }}
public class UserDto{    public string Name { get; set; }    public string Token { get; set; }}

ToDo.Server

先革新服务端,增加必要援用,编写身份认证代码等

增加援用

  • Microsoft.AspNetCore.Authentication.JwtBearer

Startup.cs

增加 JwtBearer 配置

public void ConfigureServices(IServiceCollection services){    //......    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)        .AddJwtBearer(options =>        {            options.TokenValidationParameters = new TokenValidationParameters            {                ValidateIssuer = true,//是否验证Issuer                ValidateAudience = true,//是否验证Audience                ValidateLifetime = true,//是否验证生效工夫                ValidateIssuerSigningKey = true,//是否验证SecurityKey                ValidAudience = "guetClient",//Audience                ValidIssuer = "guetServer",//Issuer,这两项和签发jwt的设置统一                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"))//拿到SecurityKey            };        });}

此处定义了 Token 的密钥,规定等,理论我的项目时能够将这些信息放到配置中。

AuthController.cs

行政验证控制器,用于验证用户身份,创立 Token 等。

[ApiController][Route("api/[controller]/[action]")]public class AuthController : ControllerBase{    //登录    [HttpPost]    public UserDto Login(LoginDto dto)    {        //模仿取得Token        var jwtToken = GetToken(dto.UserName);        return new() { Name = dto.UserName, Token = jwtToken };    }    //取得用户,当页面客户端页面刷新时调用以取得用户信息    [HttpGet]    public UserDto GetUser()    {        if (User.Identity.IsAuthenticated)//如果Token无效        {            var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value;//从Token中拿出用户ID            //模仿取得Token            var jwtToken = GetToken(name);            return new UserDto() { Name = name, Token = jwtToken };        }        else        {            return new UserDto() { Name = null, Token = null };        }    }    public string GetToken(string name)    {        //此处退出账号密码验证代码        var claims = new Claim[]        {            new Claim(ClaimTypes.Name,name),            new Claim(ClaimTypes.Role,"Admin"),        };        var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"));        var expires = DateTime.Now.AddDays(30);        var token = new JwtSecurityToken(            issuer: "guetServer",            audience: "guetClient",            claims: claims,            notBefore: DateTime.Now,            expires: expires,            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));        return new JwtSecurityTokenHandler().WriteToken(token);    }}

ToDo.Client

革新客户端,让客户端反对身份认证

增加援用

  • Microsoft.AspNetCore.Components.Authorization

AuthenticationStateProvider

AuthenticationStateProviderAuthorizeView 组件和 CascadingAuthenticationState 组件用于获取身份验证状态的根底服务。
通常不间接应用 AuthenticationStateProvider,间接应用次要毛病是,如果根底身份验证状态数据产生更改,不会主动告诉组件。其次是我的项目中总会有一些自定义的认证逻辑。
所以咱们通常写一个类继承他,并重写一些咱们本人的逻辑。

//AuthProvider.cspublic class AuthProvider : AuthenticationStateProvider{    private readonly HttpClient HttpClient;    public string UserName { get; set; }    public AuthProvider(HttpClient httpClient)    {        HttpClient = httpClient;    }    public async override Task<AuthenticationState> GetAuthenticationStateAsync()    {        //这里取得用户登录状态        var result = await HttpClient.GetFromJsonAsync<UserDto>($"api/Auth/GetUser");        if (result?.Name == null)        {            MarkUserAsLoggedOut();            return new AuthenticationState(new ClaimsPrincipal());        }        else        {            var claims = new List<Claim>();            claims.Add(new Claim(ClaimTypes.Name, result.Name));            var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));            return new AuthenticationState(authenticatedUser);        }    }    /// <summary>    /// 标记受权    /// </summary>    /// <param name="loginModel"></param>    /// <returns></returns>    public void MarkUserAsAuthenticated(UserDto userDto)    {        HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);        UserName = userDto.Name;        //此处应该依据服务器的返回的内容进行配置本地策略,作为演示,默认增加了“Admin”        var claims = new List<Claim>();        claims.Add(new Claim(ClaimTypes.Name, userDto.Name));        claims.Add(new Claim("Admin", "Admin"));        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));        NotifyAuthenticationStateChanged(authState);        //慈湖能够能够将Token存储在本地存储中,实现页面刷新无需登录    }    /// <summary>    /// 标记登记    /// </summary>    public void MarkUserAsLoggedOut()    {        HttpClient.DefaultRequestHeaders.Authorization = null;        UserName = null;        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());        var authState = Task.FromResult(new AuthenticationState(anonymousUser));        NotifyAuthenticationStateChanged(authState);    }}

NotifyAuthenticationStateChanged办法会告诉身份验证状态数据(例如 AuthorizeView)使用者应用新数据从新出现。
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);将 HTTP 申请头中退出 Token,这样之后所有的申请都会带上 Token。

Program中注入AuthProvider服务,以便于其余中央应用

//Program.csbuilder.Services.AddScoped<AuthenticationStateProvider, AuthProvider>();

Program中配置反对的策略

builder.Services.AddAuthorizationCore(option =>{    option.AddPolicy("Admin", policy => policy.RequireClaim("Admin"));});

登录界面

增加Login.razor组件,代码如下

<div style="margin:100px">  <Spin Spinning="isLoading">    @if (model != null) {    <form      OnFinish="OnSave"      Model="@model"      LabelCol="new ColLayoutParam() {Span = 6 }"    >      <FormItem Label="用户名">        <input @bind-Value="context.UserName" />      </FormItem>      <FormItem Label="明码">        <input @bind-Value="context.Password" type="password" />      </FormItem>      <FormItem WrapperColOffset="6">        <button type="@ButtonType.Primary" HtmlType="submit">登录</button>      </FormItem>    </form>    }  </Spin></div>
public partial class Login{    [Inject] public HttpClient Http { get; set; }    [Inject] public MessageService MsgSvr { get; set; }    [Inject] public AuthenticationStateProvider AuthProvider { get; set; }    LoginDto model = new LoginDto();    bool isLoading;    async void OnLogin()    {        isLoading = true;        var httpResponse = await Http.PostAsJsonAsync<LoginDto>($"api/Auth/Login", model);        UserDto result = await httpResponse.Content.ReadFromJsonAsync<UserDto>();        if (string.IsNullOrWhiteSpace(result?.Token) == false )        {            MsgSvr.Success($"登录胜利");            ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);        }        else        {            MsgSvr.Error($"用户名或明码谬误");        }        isLoading = false;       InvokeAsync( StateHasChanged);    }}

登录界面代码很简略,就是向api/Auth/Login申请,依据返回的后果判断是否登入胜利。
((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);标记身份认证状态曾经批改。

批改布局

批改MainLayout.razor文件

<CascadingAuthenticationState>  <AuthorizeView>    <Authorized>      <Layout>        <Sider Style="overflow: auto;height: 100vh;position: fixed;left: 0;">          <div class="logo">进击吧!Blazor!</div>          <menu Theme="MenuTheme.Dark" Mode="@MenuMode.Inline">            <menuitem RouterLink="/"> 主页 </menuitem>            <menuitem RouterLink="/today" RouterMatch="NavLinkMatch.Prefix">              我的一天            </menuitem>            <menuitem RouterLink="/star" RouterMatch="NavLinkMatch.Prefix">              重要工作            </menuitem>            <menuitem RouterLink="/search" RouterMatch="NavLinkMatch.Prefix">              全副            </menuitem>          </menu>        </Sider>        <Layout Class="site-layout"> @Body </Layout>      </Layout>    </Authorized>    <NotAuthorized>      <ToDo.Client.Pages.Login></ToDo.Client.Pages.Login>    </NotAuthorized>  </AuthorizeView></CascadingAuthenticationState>

当受权通过后显示<AuthorizeView><Authorized>的菜单及主页,反之显示<NotAuthorized>Login组件内容。
当须要依据权限显示不同内容,能够应用<AuthorizeView>Policy属性实现,具体是在AuthenticationStateProvider中通过配置策略,比方示例中claims.Add(new Claim("Admin", "Admin"));就增加了Admin策略,在页面上只需<AuthorizeView Policy="Admin">就能够管制只有Admin策略的账户显示其内容了。
CascadingAuthenticationState级联身份状态,它采纳了 Balzor 组件中级联机制,这样咱们能够在任意层级的组件中应用AuthorizeView来管制 UI 了
AuthorizeView 组件依据用户是否取得受权来选择性地显示 UI 内容。
Authorized组件中的内容只有在取得受权时显示。
NotAuthorized组件中的内容只有在未经受权时显示。

批改_Imports.razor文件,增加必要的援用

@using Microsoft.AspNetCore.Components.Authorization

运行查看成果

更多对于平安

平安是一个很大的话题,这个章节只是介绍了其最简略的实现形式,还有更多内容举荐浏览官网文档:
https://docs.microsoft.com/zh...

次回预报

咱们通过几张图表,将咱们 ToDo 利用中工作状况做个完满统计。

学习材料

更多对于Blazor学习材料:https://aka.ms/LearnBlazor