JWT 不会通过 Blazor 存储在 ASP.NET Core 中

Posted

技术标签:

【中文标题】JWT 不会通过 Blazor 存储在 ASP.NET Core 中【英文标题】:JWT doesn't get stored in ASP.NET Core with Blazor 【发布时间】:2019-03-12 14:19:25 【问题描述】:

我遵循了这个教程:https://medium.com/@st.mas29/microsoft-blazor-web-api-with-jwt-authentication-part-1-f33a44abab9d

我下载了例子:https://github.com/StuwiiDev/DotnetCoreJwtAuthentication/tree/Part2

我可以看到令牌已创建,但我不明白它是如何或应该保存在客户端的,因为每次我访问具有 Authorize 标记的 SampleDataController 时,它都会返回 401 .

当使用 Postman 调用和添加令牌时,它可以工作。

我缺少什么让我的用户通过身份验证? Microsoft.AspNetCore.Authentication.JwtBearer 不处理客户端部分(存储令牌)吗?

【问题讨论】:

您是否在标题中使用Authorize 作为名称?如果是,应该是Authorization 这是我不明白的部分。我按照教程下载了项目,并希望在登录后看到令牌。这个令牌应该如何设置?根据您的评论,我认为 JwtBearer 不会为我做这件事? 我理解您的困惑...本教程似乎没有使用 Authorization 标头,而是将令牌作为查询字符串参数提供。作者展示了如何在 Postman 中使用它进行测试,而不是在客户端应用程序中。他在第 2 部分末尾提到您可以将令牌保存到 localStorage,但没有给出实际使用令牌从客户端向服务器发出请求的示例。这不是一个很好或有用的 IMO 教程。 【参考方案1】:

我缺少什么让我的用户通过身份验证? Microsoft.AspNetCore.Authentication.JwtBearer 不处理客户端部分(存储令牌)吗?

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");
    

结果将是:

【讨论】:

感谢您的精彩解释,现在可以使用了!仅作记录:为了能够注入TokenService,我需要将其添加到Startup.ConfigureServices - services.AddSingleton&lt;TokenService&gt;(myTokenService);中的服务中 很好的答案,真的很有用。另外,上面的评论非常有用。我建议将它放在答案中,因为没有它就无法调用令牌服务。 @GeorgeHarnwell 感谢您的建议,我已经编辑了我的答案:)【参考方案2】:

提前道歉,因为这在某种程度上是对先前答案的回应,但我没有代表对此发表评论。

如果它可以帮助其他正在寻找在 Blazor 应用程序中使用 JWT 的解决方案的其他人,我发现 @itminus 的答案非常有用,但它也为我指明了另一门课程。

我发现的一个问题是,第二次调用 FetchData.cshtml 会在它再次尝试添加 Authorization 标头时崩溃。

我没有在此处添加默认标头,而是在成功登录后将其添加到 HttpClient 单例中(我相信 Blazor 会自动为您创建)。因此,将 SubmitForm 从 @itminus 的答案更改为 Login.cshtml

    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。

【讨论】:

【参考方案3】:

以下类处理客户端上的登录过程,将 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类,如下:

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)]  

等等……

希望这会有所帮助...

【讨论】:

以上是关于JWT 不会通过 Blazor 存储在 ASP.NET Core 中的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 B2C 和 Blazor 获取 JWT 不记名令牌

通过 javascript 在 Blazor 中更改输入值不会更改它的绑定属性值

解析 JWT 令牌以仅在 C# 或 Blazor 中没有外部库的情况下获取有效负载内容

使用通过 Blazor WASM 使用 Windows 身份验证的 WebAPI

在 ASP.NET Core 中使用基于本地存储的 JWT-Token 更改用户密码(ASP.Identity)

Blazor webassembly pwa 会话存储在部署到 Azure 后不持久