使用 Jwt 令牌身份验证在 Blazor 服务器应用程序中自定义 AuthenticationStateProvider [英] Customizing the AuthenticationStateProvider in Blazor Server App with Jwt Token Authentication

查看:39
本文介绍了使用 Jwt 令牌身份验证在 Blazor 服务器应用程序中自定义 AuthenticationStateProvider的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我注意到许多开发人员都将 AuthenticationStateProvider 子类化Blazor Server App 和 Blazor WebAssembly App 错误,而且更重要的是错误原因.

I've noticed that many developers subclass the AuthenticationStateProvider both in Blazor Server App and Blazor WebAssembly App wrongly, and more imprtantly for the wrong reasons.

如何正确执行以及何时执行?

How to do it correctly and when ?

推荐答案

首先,您不要将 AuthenticationStateProvider 子类化为唯一的目的向 ClaimPrincipal 对象添加声明.一般来说,声明是在一个用户已通过身份验证,如果您需要检查这些声明并对其进行转换,应该在其他地方完成,而不是在 AuthenticationStateProvider 对象中.顺便说一下,在Asp.Net Core 有两种方法可以做到这一点,但这本身就是一个问题.

First off, you do not subclass the AuthenticationStateProvider for the sole purpose of adding claims to the ClaimPrincipal object. Generally speaking, claims are added after a user has been authenticated, and if you need to inspect those claims and tranform them, it should be done somewhere else, not in the AuthenticationStateProvider object. Incidentally, in Asp.Net Core there are two ways how you can do that, but this merits a question of its own.

我猜这个代码示例让很多人相信这是向 ClaimsPrincipal 对象添加声明的地方.

I guess that this code sample led many to believe that this is the place to add claims to the ClaimsPrincipal object.

在当前上下文中,实现 Jwt Token Authentication,应该添加声明Jwt Token 在服务端创建,需要时在客户端提取,例如,您需要当前用户的名称.我注意到开发人员保存本地存储中的用户名,并在需要时检索它.这是错误的.您应该从 Jwt Token 中提取用户名.

In the current context, implementing Jwt Token Authentication, claims should be added to the Jwt Token when it is created on the server, and extracted on the client when required, as for instance, you need the name of the current user. I've noticed that developers save the name of the user in the local storage, and retrieved it when needed. This is wrong. You should extract the name of the user from the Jwt Token.

以下代码示例描述了如何创建自定义 AuthenticationStateProvider 对象其目标是从本地存储中检索新添加的 Jwt Token 字符串,解析其内容,并创建一个提供给感兴趣的 ClaimsPrincipal 对象各方(AuthenticationStateProvider.AuthenticationStateChanged 事件的订阅者),例如 CascadingAuthenticationState 对象.

The following code sample describes how to create a custom AuthenticationStateProvider object whose objective is to retrieve from the local storage a Jwt Token string that has newly added, parse its content, and create a ClaimsPrincipal object that is served to interested parties (subscribers to the AuthenticationStateProvider.AuthenticationStateChanged event) , such as the CascadingAuthenticationState object.

以下代码示例演示了如何实现自定义authenticationstateprovider 正确,并且有充分的理由.

The following code sample demonstrates how you can implement a custom authenticationstateprovider properly, and for good reason.

public class TokenServerAuthenticationStateProvider : 
                                AuthenticationStateProvider
    {
        private readonly IJSRuntime _jsRuntime;
       
        public TokenServerAuthenticationStateProvider(IJSRuntime jsRuntime)
        {
            _jsRuntime = jsRuntime;
           
           
        }

       public async Task<string> GetTokenAsync()
            => await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");

        public async Task SetTokenAsync(string token)
        {
            if (token == null)
            {
                await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
            }
            else
            {
                await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
            }
            
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var token = await GetTokenAsync();
            var identity = string.IsNullOrEmpty(token)
                ? new ClaimsIdentity()
                : new ClaimsIdentity(ServiceExtensions.ParseClaimsFromJwt(token), "jwt");
            return new AuthenticationState(new ClaimsPrincipal(identity));
        }
    }

这是一个位于登录页面的提交按钮中的代码示例,调用验证用户凭据的 Web Api 端点,然后一个 Jwt Token 被创建并传递回调用代码:

And here's a code sample residing in the submit button of a Login page that calls a Web Api endpoint where the user credentials are validated, after which a Jwt Token is created and passed back to the calling code:

async Task SubmitCredentials()
{

    bool lastLoginFailed;

    var httpClient = clientFactory.CreateClient();
    httpClient.BaseAddress = new Uri("https://localhost:44371/");

    var requestJson = JsonSerializer.Serialize(credentials, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });


    var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "api/user/login")
    {
        Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
    });

    var stringContent = await response.Content.ReadAsStringAsync();

    var result = JsonSerializer.Deserialize<LoginResult>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

    lastLoginFailed = result.Token == null;
    if (!lastLoginFailed)
    {
        // Success! Store token in underlying auth state service
        await TokenProvider.SetTokenAsync(result.Token);
        NavigationManager.NavigateTo(ReturnUrl);
        
    }
}

Point to note: TokenProvider is an instance of TokenServerAuthenticationStateProvider. 
Its name reflects its functionality: handling the recieved Jwt Token, and providing 
the Access Token when requested.

This line of code: TokenProvider.SetTokenAsync(result.Token); passes the Jwt Token 
to TokenServerAuthenticationStateProvider.SetTokenAsync in which the token is sored 
in the local storage, and then raises AuthenticationStateProvider.AuthenticationStateChanged
event by calling NotifyAuthenticationStateChanged, passing an AuthenticationState object
built from the data contained in the stored Jwt Token.


Note that the GetAuthenticationStateAsync method creates a new ClaimsIdentity object from 
the parsed Jwt Token. All the claims added to the newly created ClaimsIdentity object 
are retrieved from the Jwt Token. I cannot think of a use case where you have to create
a new claim object and add it to the ClaimsPrincipal object.

The following code is executed when an authenticated user is attempting to access
the FecthData page

@code 
{
   private WeatherForecast[] forecasts;


protected override async Task OnInitializedAsync()
{
    var token = await TokenProvider.GetTokenAsync();
   
    var httpClient = clientFactory.CreateClient();
    httpClient.BaseAddress = new Uri("https://localhost:44371/");
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"api/WeatherForecast?startDate={DateTime.Now}"));
    
    var stringContent = await response.Content.ReadAsStringAsync();

    forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
   
}

}

注意第一行代码:var token = await TokenProvider.GetTokenAsync(); 检索本地存储的Jwt Token,添加到请求的Authorization header中.

Note that the first line of code: var token = await TokenProvider.GetTokenAsync(); retrieves the Jwt Token stored in the local storage, and add it to the Authorization header of the request.

希望这有帮助...

注意:ServiceExtensions.ParseClaimsFromJwt 是获取从本地存储中提取的 Jwt 令牌的方法,并将其解析为声明集合.

Note: ServiceExtensions.ParseClaimsFromJwt is a method that gets the Jwt token extracted from the local storage, and parse it into a collection of claims.

你的 Startup 类应该是这样的:

Your Startup class should be like this:

public void ConfigureServices(IServiceCollection services)
   {
      // Code omitted...

      services.AddScoped<TokenServerAuthenticationStateProvider>();
      services.AddScoped<AuthenticationStateProvider>(provider =>  provider.GetRequiredService<TokenServerAuthenticationStateProvider>());

  }

这篇关于使用 Jwt 令牌身份验证在 Blazor 服务器应用程序中自定义 AuthenticationStateProvider的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆