准备工作:

安装Nuget包:Blazored.LocalStorge。

这是一个client-side 浏览器存储库,找了非常久。

官方文档中也有一款Microsoft.AspNetCore.ProtectedBrowserStorage,具有相同功能,代码实现的方式都是通过dotnet 和 js 互操作,使用sessionStorage,官方依然不推荐使用这个包,但是却没有提供其他方式。

安装nuget包:Microsoft.AspNetCore.Components.Authorization。

继承并实现StatusProvider

    public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage; public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken"); if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
} _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken); return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
} public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
} public void MarkUserAsLoggedOut()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
} private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles); if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString()); foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
} keyValuePairs.Remove(ClaimTypes.Role);
} claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); return claims;
} private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % )
{
case : base64 += "=="; break;
case : base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}

创建AuthService,用于在页面中使用。同时先创建IAuthService接口

    public interface IAuthService
{
Task<LoginResult> Login(LoginModel loginModel);
Task Logout();
Task<RegisterResult> Register(RegisterModel registerModel);
}

实现:

    public class AuthService : IAuthService
{
private readonly HttpClient _httpClient;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly ILocalStorageService _localStorage; public AuthService(HttpClient httpClient,
AuthenticationStateProvider authenticationStateProvider,
ILocalStorageService localStorage)
{
_httpClient = httpClient;
_authenticationStateProvider = authenticationStateProvider;
_localStorage = localStorage;
} public async Task<RegisterResult> Register(RegisterModel registerModel)
{
var result = await _httpClient.PostJsonAsync<RegisterResult>($"{Program.ServerUrl}/api/register", registerModel); return result;
} public async Task<LoginResult> Login(LoginModel loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync($"{Program.ServerUrl}/api/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (!response.IsSuccessStatusCode)
{
return loginResult;
} await _localStorage.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token); return loginResult;
} public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
 

最后将上面的服务都注入,由于不同模版生产的Program.cs不太一样,这里只展示我自己的Program.cs

    public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app"); //注入服务
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
builder.Services.AddScoped<IAuthService, AuthService>(); await builder.Build().RunAsync();
} public const string ServerUrl = "https://localhost:5002";
}
 

添加页面

接下来添加Login.razor和Register.razor。

Login.razor

@page "/login"
@using client.Interfaces
@using shared @inject IAuthService AuthService
@inject NavigationManager NavigationManager <h1>Login</h1> @if (ShowErrors)
{
<div class="alert alert-danger" role="alert">
<p>@Error</p>
</div>
} <div class="card">
<div class="card-body">
<h5 class="card-title">Please enter your details</h5>
<EditForm Model="loginModel" OnValidSubmit="HandleLogin">
<DataAnnotationsValidator />
<ValidationSummary /> <div class="form-group">
<label for="email">Email address</label>
<InputText Id="email" Class="form-control" @bind-Value="loginModel.Email" />
<ValidationMessage For="@(() => loginModel.Email)" />
</div>
<div class="form-group">
<label for="password">Password</label>
<InputText Id="password" type="password" Class="form-control" @bind-Value="loginModel.Password" />
<ValidationMessage For="@(() => loginModel.Password)" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
</div>
</div> @code {
[Parameter]
public string returnUrl { get; set; } private LoginModel loginModel = new LoginModel();
private bool ShowErrors;
private string Error = ""; /// <summary>
/// 登陆点击事件
/// </summary>
/// <returns></returns>
private async Task HandleLogin()
{
ShowErrors = false; var result = await AuthService.Login(loginModel); if (result.Successful)
{
if (!string.IsNullOrEmpty(returnUrl))
{
NavigationManager.NavigateTo($"/{returnUrl}");
}
else
{
NavigationManager.NavigateTo("/");
}
}
else
{
Error = result.Error;
ShowErrors = true;
}
} }

Register.razor

@page "/register"
@using client.Interfaces
@using shared
@inject IAuthService AuthService
@inject NavigationManager NavigationManager <h1>Register</h1> @if (ShowErrors)
{
<div class="alert alert-danger" role="alert">
@foreach (var error in Errors)
{
<p>@error</p>
}
</div>
} <div class="card">
<div class="card-body">
<h5 class="card-title">Please enter your details</h5>
<EditForm Model="RegisterModel" OnValidSubmit="HandleRegistration">
<DataAnnotationsValidator />
<ValidationSummary /> <div class="form-group">
<label for="email">Email address</label>
<InputText Id="email" class="form-control" @bind-Value="RegisterModel.Email" />
<ValidationMessage For="@(() => RegisterModel.Email)" />
</div>
<div class="form-group">
<label for="password">Password</label>
<InputText Id="password" type="password" class="form-control" @bind-Value="RegisterModel.Password" />
<ValidationMessage For="@(() => RegisterModel.Password)" />
</div>
<div class="form-group">
<label for="password">Confirm Password</label>
<InputText Id="password" type="password" class="form-control" @bind-Value="RegisterModel.ConfirmPassword" />
<ValidationMessage For="@(() => RegisterModel.ConfirmPassword)" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
</div>
</div> @code { private RegisterModel RegisterModel = new RegisterModel();
private bool ShowErrors;
private IEnumerable<string> Errors; private async Task HandleRegistration()
{
ShowErrors = false; var result = await AuthService.Register(RegisterModel); if (result.Successful)
{
NavigationManager.NavigateTo("/login");
}
else
{
Errors = result.Errors;
ShowErrors = true;
}
} }

完成后,我们需要创建一个AuthorizeView组件,用来显示是否登陆。AuthorizeView是一个集成组件,它会自动根据登陆状态显示不同内容,前提是我们前面实现并注入的AuthenticationStateProvider。

它的结构类似这样:

<AuthorizeView>
<Authorized>
<!--加入已登录后的内容-->
Hello, @context.User.Identity.Name!
<a href="LogOut">Log out</a>
</Authorized>
<NotAuthorized>
<!--加入未登录的内容-->
<a href="Register">Register</a>
<a href="Login">Log in</a>
</NotAuthorized>
</AuthorizeView>

我们将上面的内容放入Component文件夹(或者shared,这个取决于你自己),我这里取名为:LoginDisplay.razor。

这时候你会看到错误,不存在这个TagHelper,这是因为我们还没有导入到razor中,打开_import.razor

添加一些引用:

@using Microsoft.AspNetCore.Components.Authorization
@using Blazored.LocalStorage
@using client.Services
@using client.Providers //取决于你的项目名

再将LoginDisplay组件放到MainLayout.razor中Microsoft.AspNetCore.Components.Authorization

@inherits LayoutComponentBase

<div class="sidebar">
<NavMenu />
</div> <div class="main">
<div class="top-row px-4">
<LoginDisplay></LoginDisplay> //放入
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div> <div class="content px-4">
@Body
</div>
</div>

完成上面后,客户端的内容基本已经完成,现在可以开始测试了。

tips:如果你在使用Blazor wasm 3.2 preview2 ,且Microsoft.AspNetCore.Components.Authorization 版本为3.1.2,那你可能会遇到跟我一样的问题,上述代码可能可能无法loading。浏览器控制台输出提示无法找到此组件。

这时候就需要给修改一下注入。

            builder.Services.AddAuthorizationCore(options => { });

解决方案来自:https://github.com/dotnet/aspnetcore/issues/18733

应该是一个bug,因为当我换成3.1.0 preview4时,代码就正常能运行。

然后运行代码即可,项目完整源码:https://github.com/simplerjiang/AuthApiAndBlazor

Blazor client-side + webapi (.net core 3.1) 添加jwt验证流程(非host)第三章 客户端存储及验证的更多相关文章

  1. ASP.NET Core 6.0 添加 JWT 认证和授权

    序言 本文将分别介绍 Authentication(认证) 和 Authorization(授权). 并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能. 相关 ...

  2. Blazor client-side + webapi (.net core 3.1) 添加jwt验证流程(非host)第二步 添加Identity

    添加Identity数据上下文 安装nuget包:Microsoft.AspNetCore.Identity.EntityFrameworkCore 创建ApplicationDbContext类 创 ...

  3. .net core webapi+EF Core

    .net core webapi+EF Core 一.描述: EF Core必须下载.net core2.0版本 Micorsoft.EntityFrameworkCore:EF框架的核心包Micor ...

  4. 【Blazor】在ASP.NET Core中使用Blazor组件 - 创建一个音乐播放器

    前言 Blazor正式版的发布已经有一段时间了,.NET社区的各路高手也创建了一个又一个的Blazor组件库,其中就包括了我和其他小伙伴一起参与的AntDesign组件库,于上周终于发布了第一个版本0 ...

  5. 007.Adding a view to an ASP.NET Core MVC app -- 【在asp.net core mvc中添加视图】

    Adding a view to an ASP.NET Core MVC app 在asp.net core mvc中添加视图 2017-3-4 7 分钟阅读时长 本文内容 1.Changing vi ...

  6. 【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了基于Ids4密码授权模式,从使用场景.原理分析.自定义帐户体系集成完整的介绍了密码授权模式的内容,并最后给出了三个思考问题,本 ...

  7. ASP.NET Core Web Api之JWT刷新Token(三)

    前言 如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋 ...

  8. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理三 (二十一)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  9. bp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

随机推荐

  1. 对于Python中的字节串bytes和字符串以及转义字符的新的认识

    事情的起因是之前同学叫我帮他用Python修改一个压缩包的二进制内容用来做fuzz,根据他的要求,把压缩包test.rar以十六进制的方式打开,每次修改其中一个十六进制字符串并保存为一个新的rar用来 ...

  2. Python3中的super()函数详解

    关于Python3中的super()函数 我们都知道,在Python3中子类在继承父类的时候,当子类中的方法与父类中的方法重名时,子类中的方法会覆盖父类中的方法, 那么,如果我们想实现同时调用父类和子 ...

  3. Spring框架学习笔记(8)——spring boot+mybatis plus+mysql项目环境搭建

    之前写的那篇Spring框架学习笔记(5)--Spring Boot创建与使用,发现有多小细节没有提及,,正好现在又学习了mybatis plus这款框架,打算重新整理一遍,并将细节说清楚 1.通过I ...

  4. Java 分布式框架面试题合集

    Java 分布式框架面试题合集 1.什么是 ZooKeeper? 答:ZooKeeper 是一个开源的分布式应用程序协调服务,是一个典型的分布式数据一致性解决方案.设计目的是将那些复杂且容易出错的分布 ...

  5. "@阅后即焚"上线了!

    前一阵发现了一个有趣的网站,他可以让你的文字在显示一次后销毁. 直到我把网站发给一个朋友,网站打不开了,于是就想着开发一个. 前端用的bootstrap这个框架,后端用PHP写的,没有后台,现在还不需 ...

  6. 奇葩报错0xc0000142

    电脑突然蓝屏了一次,后来软件就打不开了,显示无法启动 网上找了一下说估计是蓝屏出现了一些问题注册表信息被删了,让输入命令重新加载一回注册表信息 for %1 in (%windir%\system32 ...

  7. android webview正确显示音标

    package com.example.fonttest; import android.support.v7.app.ActionBarActivity; import android.webkit ...

  8. 再谈typedef(重点为函数指针)

    有种很方便的写法. typedef int *p: p pointer: 这时直接把pointer带入原式中,取代p然后去掉typedef,得到的结果就是int * pointer: 哈哈,这样直接替 ...

  9. Linux系统之LAMP实现

    1.部署分离的LAMP,部署到二台服务器上,php加载xcache模块 首先准备LAMP环境,准备两台Linux主机,一台是192.168.0.10 ,这台上面主要跑apache httpd ,mar ...

  10. 关于Icon,Image,ImageIcon的简单的对比参考

    Icon: Icon位于javax.swing包中,它是一个接口 public interface Icon,介绍为:一个小的固定大小的图片,通常用于装饰组件 有三个方法: int getIconHe ...