首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用Jwt令牌身份验证自定义Blazor Server App中的AuthenticationStateProvider

使用Jwt令牌身份验证自定义Blazor Server App中的AuthenticationStateProvider
EN

Stack Overflow用户
提问于 2020-06-23 14:59:46
回答 1查看 4.9K关注 0票数 9

我注意到许多开发人员在Blazor Server App和Blazor WebAssembly App中都错误地将AuthenticationStateProvider子类化,更明显的是出于错误的原因。

怎样做才是正确的,什么时候做?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-06-23 14:59:46

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

我猜是this code sample让很多人相信这是向ClaimsPrincipal对象添加声明的地方。

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

下面的代码示例描述如何创建自定义Jwt对象,该对象的目标是从本地存储区检索新添加的AuthenticationStateProvider令牌字符串,分析其内容,并创建供相关方( AuthenticationStateProvider.AuthenticationStateChanged事件的订阅者)使用的ClaimsPrincipal对象(如CascadingAuthenticationState对象)。

下面的代码示例演示了如何正确地、有理由地实现自定义身份验证状态提供程序。

代码语言:javascript
运行
复制
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));
        }
    }

下面是一个驻留在Login页面的submit按钮中的代码示例,它调用一个Web Api端点,在那里验证用户凭据,然后创建一个Jwt令牌并将其传递回调用代码:

代码语言:javascript
运行
复制
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令牌,并将其添加到请求的Authorization中。

希望这能帮到你。

编辑

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

您的启动类应该如下所示:

代码语言:javascript
运行
复制
public void ConfigureServices(IServiceCollection services)
   {
      // Code omitted...

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

  }
票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62529029

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档