JWT 不会使用 Blazor 存储在 ASP.NET Core 中
JWT doesn't get stored in ASP.NET Core with Blazor
我遵循了这个教程:https://medium.com/@st.mas29/microsoft-blazor-web-api-with-jwt-authentication-part-1-f33a44abab9d
我下载了示例:https://github.com/StuwiiDev/DotnetCoreJwtAuthentication/tree/Part2
我可以看到令牌已创建,但我不明白它是如何或应该如何保存在客户端的,因为每次我访问 SampleDataController
,其中有 Authorize
标记,它 returns 一个 401。
使用 Postman 调用和添加令牌时有效。
要对我的用户进行身份验证,我缺少什么? Microsoft.AspNetCore.Authentication.JwtBearer
不处理客户端部分(存储令牌)吗?
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
JwtBearer
在服务器端运行,它只会验证请求的授权头,即Authorization: Bearer your_access_token
,而不会关心你的WebAssembly代码如何运行。因此,您需要使用 jwt accessToken 发送请求。由于本教程建议您应该使用 localStorage
,让我们将 accessToken
与 localStorage
一起存储。
因为 WebAssembly
还没有访问 BOM
的权限,我们需要一些 javascript 代码作为胶水。为此,在 JwtAuthentication.Client/wwwroot/js/
下添加一个 helper.js
:
var wasmHelper = {};
wasmHelper.ACCESS_TOKEN_KEY ="__access_token__";
wasmHelper.saveAccessToken = function (tokenStr) {
localStorage.setItem(wasmHelper.ACCESS_TOKEN_KEY,tokenStr);
};
wasmHelper.getAccessToken = function () {
return localStorage.getItem(wasmHelper.ACCESS_TOKEN_KEY);
};
并在您的 JwtAuthentication.Client/wwwroot/index.html
中引用脚本
<body>
<app>Loading...</app>
<script src="js/helper.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
现在,让我们将 javascript 代码包装到 C# 中。创建一个新文件 Client/Services/TokenService.cs
:
public class TokenService
{
public Task SaveAccessToken(string accessToken) {
return JSRuntime.Current.InvokeAsync<object>("wasmHelper.saveAccessToken",accessToken);
}
public Task<string> GetAccessToken() {
return JSRuntime.Current.InvokeAsync<string>("wasmHelper.getAccessToken");
}
}
通过以下方式注册此服务:
// file: Startup.cs
services.AddSingleton<TokenService>(myTokenService);
现在我们可以将 TokenService
注入 Login.cshtml
并用它来保存令牌:
@using JwtAuthentication.Client.Services
// ...
@page "/login"
// ...
@inject TokenService tokenService
// ...
@functions {
public string Email { get; set; } = "";
public string Password { get; set; } = "";
public string Token { get; set; } = "";
/// <summary>
/// response from server
/// </summary>
private class TokenResponse{
public string Token;
}
private async Task SubmitForm()
{
var vm = new TokenViewModel
{
Email = Email,
Password = Password
};
var response = await Http.PostJsonAsync<TokenResponse>("http://localhost:57778/api/Token", vm);
await tokenService.SaveAccessToken(response.Token);
}
}
假设您要在 FetchData.cshtml
内发送数据
@functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
var token = await tokenService.GetAccessToken();
Http.DefaultRequestHeaders.Add("Authorization",String.Format("Bearer {0} ",token));
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
结果将是:
以下 class 处理客户端上的登录过程,将 JWT 令牌存储在 local
存储中。注意:开发人员有责任存储 JWT 令牌并将其传递给服务器。客户端(Blazor、Angular 等)不会自动为他执行此操作。
public class SignInManager
{
// Receive 'http' instance from DI
private readonly HttpClient http;
public SignInManager(HttpClient http)
{
this.http = http;
}
[Inject]
protected LocalStorage localStorage;
public bool IsAuthenticated()
{
var token = localStorage.GetItem<string>("token");
return (token != null);
}
public string getToken()
{
return localStorage.GetItem<string>("token");
}
public void Clear()
{
localStorage.Clear();
}
// model.Email, model.Password, model.RememberMe, lockoutOnFailure: false
public async Task<bool> PasswordSignInAsync(LoginViewModel model)
{
SearchInProgress = true;
NotifyStateChanged();
var result = await http.PostJsonAsync<Object>("/api/Account", model);
if (result)// result.Succeeded
{
_logger.LogInformation("User logged in.");
// Save the JWT token in the LocalStorage
// https://github.com/BlazorExtensions/Storage
await localStorage.SetItem<Object>("token", result);
// Returns true to indicate the user has been logged in and the JWT token
// is saved on the user browser
return true;
}
}
}
// 这就是您调用 Web API 的方式,向它发送 // 当前用户的 JWT 令牌
public async Task<IList<Profile>> GetProfiles()
{
SearchInProgress = true;
NotifyStateChanged();
var token = signInManager.getToken();
if (token == null) {
throw new ArgumentNullException(nameof(AppState)); //"No token";
}
this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// .set('Content-Type', 'application/json')
// this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Profiles = await this.http.GetJsonAsync<Profile[]>("/api/Profiles");
SearchInProgress = false;
NotifyStateChanged();
}
// 您还必须在客户端上设置 Startup class,如下所示:
public void ConfigureServices(IServiceCollection services)
{
// Add Blazor.Extensions.Storage
// Both SessionStorage and LocalStorage are registered
// https://github.com/BlazorExtensions/Storage
**services.AddStorage();**
...
}
// 一般而言,这是您必须在客户端上执行的操作。 // 在服务器上,你必须有一个方法,比如在 Account 控制器中,它的功能是生成 JWT 令牌,你必须配置 JWT 中间件,用必要的属性注释你的控制器,至于实例:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
等等...
希望这对您有所帮助...
提前致歉,因为这在某种程度上回应了之前的回答,但我没有代表对此发表评论。
如果它能帮助其他同样在寻找在 Blazor 应用程序中使用 JWT 的解决方案的人,我发现 @itminus 的回答非常有用,但它也让我想到了另一门课程。
我发现的一个问题是第二次调用 FetchData.cshtml
时会在尝试添加 Authorization
header 时崩溃。
我没有在此处添加默认值 header,而是在成功登录后将其添加到 HttpClient 单例(我相信 Blazor 会自动为您创建)。因此,从@itminus 的回答中更改 Login.cshtml
中的 SubmitForm
。
protected async Task SubmitForm()
{
// Remove any existing Authorization headers
Http.DefaultRequestHeaders.Remove("Authorization");
TokenViewModel vm = new TokenViewModel()
{
Email = Email,
Password = Password
};
TokenResponse response = await Http.PostJsonAsync<TokenResponse>("api/Token/Login", vm);
// Now add the token to the Http singleton
Http.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0} ", response.Token));
}
然后我意识到,在构建 SPA 时,我根本不需要跨请求保留令牌 - 它只是附加到 HttpClient。
我遵循了这个教程:https://medium.com/@st.mas29/microsoft-blazor-web-api-with-jwt-authentication-part-1-f33a44abab9d
我下载了示例:https://github.com/StuwiiDev/DotnetCoreJwtAuthentication/tree/Part2
我可以看到令牌已创建,但我不明白它是如何或应该如何保存在客户端的,因为每次我访问 SampleDataController
,其中有 Authorize
标记,它 returns 一个 401。
使用 Postman 调用和添加令牌时有效。
要对我的用户进行身份验证,我缺少什么? Microsoft.AspNetCore.Authentication.JwtBearer
不处理客户端部分(存储令牌)吗?
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
JwtBearer
在服务器端运行,它只会验证请求的授权头,即Authorization: Bearer your_access_token
,而不会关心你的WebAssembly代码如何运行。因此,您需要使用 jwt accessToken 发送请求。由于本教程建议您应该使用 localStorage
,让我们将 accessToken
与 localStorage
一起存储。
因为 WebAssembly
还没有访问 BOM
的权限,我们需要一些 javascript 代码作为胶水。为此,在 JwtAuthentication.Client/wwwroot/js/
下添加一个 helper.js
:
var wasmHelper = {};
wasmHelper.ACCESS_TOKEN_KEY ="__access_token__";
wasmHelper.saveAccessToken = function (tokenStr) {
localStorage.setItem(wasmHelper.ACCESS_TOKEN_KEY,tokenStr);
};
wasmHelper.getAccessToken = function () {
return localStorage.getItem(wasmHelper.ACCESS_TOKEN_KEY);
};
并在您的 JwtAuthentication.Client/wwwroot/index.html
<body>
<app>Loading...</app>
<script src="js/helper.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
现在,让我们将 javascript 代码包装到 C# 中。创建一个新文件 Client/Services/TokenService.cs
:
public class TokenService
{
public Task SaveAccessToken(string accessToken) {
return JSRuntime.Current.InvokeAsync<object>("wasmHelper.saveAccessToken",accessToken);
}
public Task<string> GetAccessToken() {
return JSRuntime.Current.InvokeAsync<string>("wasmHelper.getAccessToken");
}
}
通过以下方式注册此服务:
// file: Startup.cs
services.AddSingleton<TokenService>(myTokenService);
现在我们可以将 TokenService
注入 Login.cshtml
并用它来保存令牌:
@using JwtAuthentication.Client.Services
// ...
@page "/login"
// ...
@inject TokenService tokenService
// ...
@functions {
public string Email { get; set; } = "";
public string Password { get; set; } = "";
public string Token { get; set; } = "";
/// <summary>
/// response from server
/// </summary>
private class TokenResponse{
public string Token;
}
private async Task SubmitForm()
{
var vm = new TokenViewModel
{
Email = Email,
Password = Password
};
var response = await Http.PostJsonAsync<TokenResponse>("http://localhost:57778/api/Token", vm);
await tokenService.SaveAccessToken(response.Token);
}
}
假设您要在 FetchData.cshtml
@functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
var token = await tokenService.GetAccessToken();
Http.DefaultRequestHeaders.Add("Authorization",String.Format("Bearer {0} ",token));
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
结果将是:
以下 class 处理客户端上的登录过程,将 JWT 令牌存储在 local
存储中。注意:开发人员有责任存储 JWT 令牌并将其传递给服务器。客户端(Blazor、Angular 等)不会自动为他执行此操作。
public class SignInManager
{
// Receive 'http' instance from DI
private readonly HttpClient http;
public SignInManager(HttpClient http)
{
this.http = http;
}
[Inject]
protected LocalStorage localStorage;
public bool IsAuthenticated()
{
var token = localStorage.GetItem<string>("token");
return (token != null);
}
public string getToken()
{
return localStorage.GetItem<string>("token");
}
public void Clear()
{
localStorage.Clear();
}
// model.Email, model.Password, model.RememberMe, lockoutOnFailure: false
public async Task<bool> PasswordSignInAsync(LoginViewModel model)
{
SearchInProgress = true;
NotifyStateChanged();
var result = await http.PostJsonAsync<Object>("/api/Account", model);
if (result)// result.Succeeded
{
_logger.LogInformation("User logged in.");
// Save the JWT token in the LocalStorage
// https://github.com/BlazorExtensions/Storage
await localStorage.SetItem<Object>("token", result);
// Returns true to indicate the user has been logged in and the JWT token
// is saved on the user browser
return true;
}
}
}
// 这就是您调用 Web API 的方式,向它发送 // 当前用户的 JWT 令牌
public async Task<IList<Profile>> GetProfiles()
{
SearchInProgress = true;
NotifyStateChanged();
var token = signInManager.getToken();
if (token == null) {
throw new ArgumentNullException(nameof(AppState)); //"No token";
}
this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// .set('Content-Type', 'application/json')
// this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Profiles = await this.http.GetJsonAsync<Profile[]>("/api/Profiles");
SearchInProgress = false;
NotifyStateChanged();
}
// 您还必须在客户端上设置 Startup class,如下所示:
public void ConfigureServices(IServiceCollection services)
{
// Add Blazor.Extensions.Storage
// Both SessionStorage and LocalStorage are registered
// https://github.com/BlazorExtensions/Storage
**services.AddStorage();**
...
}
// 一般而言,这是您必须在客户端上执行的操作。 // 在服务器上,你必须有一个方法,比如在 Account 控制器中,它的功能是生成 JWT 令牌,你必须配置 JWT 中间件,用必要的属性注释你的控制器,至于实例:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
等等...
希望这对您有所帮助...
提前致歉,因为这在某种程度上回应了之前的回答,但我没有代表对此发表评论。
如果它能帮助其他同样在寻找在 Blazor 应用程序中使用 JWT 的解决方案的人,我发现 @itminus 的回答非常有用,但它也让我想到了另一门课程。
我发现的一个问题是第二次调用 FetchData.cshtml
时会在尝试添加 Authorization
header 时崩溃。
我没有在此处添加默认值 header,而是在成功登录后将其添加到 HttpClient 单例(我相信 Blazor 会自动为您创建)。因此,从@itminus 的回答中更改 Login.cshtml
中的 SubmitForm
。
protected async Task SubmitForm()
{
// Remove any existing Authorization headers
Http.DefaultRequestHeaders.Remove("Authorization");
TokenViewModel vm = new TokenViewModel()
{
Email = Email,
Password = Password
};
TokenResponse response = await Http.PostJsonAsync<TokenResponse>("api/Token/Login", vm);
// Now add the token to the Http singleton
Http.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0} ", response.Token));
}
然后我意识到,在构建 SPA 时,我根本不需要跨请求保留令牌 - 它只是附加到 HttpClient。