乐趣区

关于.net:进击吧Blazor系列入门教程-第一章-6安全

《进击吧!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.cs
public 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.cs
builder.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

退出移动版