Isa*_*aac 7 asp.net-web-api asp.net-core blazor blazor-server-side blazor-client-side
我注意到许多开发人员在 Blazor Server App 和 Blazor WebAssembly App 中错误地将 AuthenticationStateProvider 子类化,并且更重要的是出于错误的原因。
如何正确地做,什么时候做?
首先,您不会仅仅为了向 ClaimPrincipal 对象添加声明而对 AuthenticationStateProvider 进行子类化。一般来说,声明是在用户通过身份验证后添加的,如果您需要检查这些声明并对其进行转换,则应该在其他地方完成,而不是在 AuthenticationStateProvider 对象中。顺便提一下,在 Asp.Net Core 中有两种方法可以做到这一点,但这本身就是一个问题。
我猜这个代码示例让很多人相信这是向 ClaimsPrincipal 对象添加声明的地方。
在当前上下文中,实现Jwt Token Authentication,需要在服务端创建Jwt Token时添加声明,需要时在客户端提取,比如需要当前用户的名字。我注意到开发人员将用户名保存在本地存储中,并在需要时检索它。这是错误的。您应该从 Jwt 令牌中提取用户的名称。
以下代码示例描述了如何创建自定义 AuthenticationStateProvider 对象,其目的是从本地存储中检索新添加的 Jwt Token 字符串,解析其内容,并创建提供给相关方(AuthenticationStateProvider 的订阅者)的 ClaimsPrincipal 对象.AuthenticationStateChanged 事件),例如 CascadingAuthenticationState 对象。
以下代码示例演示了如何正确实现自定义身份验证状态提供程序,并且有充分的理由。
public class TokenServerAuthenticationStateProvider : ServerAuthenticationStateProvider
{
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));
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个位于登录页面的提交按钮中的代码示例,它调用验证用户凭据的 Web Api 端点,之后创建 Jwt 令牌并将其传递回调用代码:
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 });
}
Run Code Online (Sandbox Code Playgroud)
}
注意第一行代码:var token = await TokenProvider.GetTokenAsync();检索本地存储中存储的Jwt Token,并将其添加到请求的Authorization头中。
希望这可以帮助...
注意:ServiceExtensions.ParseClaimsFromJwt 是获取从本地存储中提取的 Jwt 令牌的方法,并将其解析为声明的集合。
| 归档时间: |
|
| 查看次数: |
3802 次 |
| 最近记录: |