Некоторые методы API требуют Токен доступа JWT для авторизации. Этот токен необходимо обновить с помощью токена обновления, и он хранится в ProtectedLocalStorage.
Первое решение
Чтобы справиться с этим, я использую [Header («Аутентификация: носитель»)] в клиентских интерфейсах. Например:
Код: Выделить всё
[Post("/")]
[Headers("Authorization: Bearer")]
Task PostPerson([Body] PersonRequest body);
AuthenticationTokenProvider — это статический класс, который выглядит следующим образом:
Код: Выделить всё
public static class AuthenticationTokenProvider
{
private static Func? _getTokenAsyncFunc;
public static void SetTokenGetterFunc(Func getTokenAsyncFunc)
{
_getTokenAsyncFunc = getTokenAsyncFunc;
}
public static Task GetTokenAsync(CancellationToken cancellationToken)
{
if (_getTokenAsyncFunc is null)
{
throw new InvalidOperationException("Token getter func must be set before using it");
}
return _getTokenAsyncFunc!(cancellationToken);
}
}
Код: Выделить всё
AuthenticationTokenProvider.SetTokenGetterFunc(_ =>
{
using (var scope = app.Services.CreateScope())
{
IAuthenticationService service = scope.ServiceProvider.GetRequiredService();
return service.GetRawAccessTokenAsync();
}
});
Код: Выделить всё
services.AddScoped();
services.AddScoped();
Код: Выделить всё
public class AuthenticationService : IAuthenticationService
{
private readonly ITokensService _tokensService;
private readonly IAuthenticationClient _authenticationClient;
public AuthenticationService(ITokensService tokensService, IAuthenticationClient authenticationClient)
{
_tokensService = tokensService;
_authenticationClient = authenticationClient;
}
public async Task GetRawAccessTokenAsync()
{
string? accessToken = await _tokensService.GetAccessToken();
if (!string.IsNullOrWhiteSpace(accessToken) && ValidateToken(accessToken))
{
return accessToken;
}
string? refreshToken = await _tokensService.GetRefreshToken();
if (string.IsNullOrWhiteSpace(refreshToken))
{
return string.Empty;
}
IApiResponse refreshResponse = await _authenticationClient.AuthenticateRefresh(new AccountAuthenticateRefreshRequest
{
AccessToken = accessToken,
RefreshToken = refreshToken
});
if (refreshResponse.IsSuccessful)
{
await UpdateTokens(refreshResponse.Content);
return refreshResponse.Content.AccessToken;
}
return string.Empty;
}
private async Task UpdateTokens(AccountAuthenticateResponse tokens) => await Task.WhenAll(
[
_tokensService.SetAccessToken(tokens.AccessToken),
_tokensService.SetRefreshToken(tokens.RefreshToken)
]);
private static bool ValidateToken(string token)
{
IEnumerable claims = GetClaimsFromToken(token);
Claim? claim = claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp);
if (claim is null || !long.TryParse(claim.Value, out long expiration))
{
return false;
}
DateTime expirationDate = DateTime.MinValue.AddTicks(expiration);
return expirationDate > DateTime.UtcNow;
}
private static IEnumerable GetClaimsFromToken(string token)
{
string payload = token.Split('.')[1];
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] jsonBytes = Convert.FromBase64String(payload);
Dictionary? keyValuePairs = JsonSerializer.Deserialize(jsonBytes);
if (keyValuePairs is null)
{
return [];
}
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
}
Код: Выделить всё
TokensService:
public class TokensService : ITokensService
{
private readonly ILogger _logger;
private readonly ProtectedLocalStorage _localStorageService;
private readonly Configuration.Tokens _configuration;
public TokensService(ILogger logger, IConfiguration configuration, ProtectedLocalStorage localStorageService)
{
_logger = logger;
_localStorageService = localStorageService;
_configuration = configuration.GetSection("Tokens").Get()!;
}
public async Task GetAccessToken() => await GetValueAsync(_configuration.StorageKeys.AccessToken);
public async Task SetAccessToken(string accessToken) => await _localStorageService.SetAsync(_configuration.StorageKeys.AccessToken, accessToken);
public async Task DeleteAccessToken() => await _localStorageService.DeleteAsync(_configuration.StorageKeys.AccessToken);
public async Task GetRefreshToken() => await GetValueAsync(_configuration.StorageKeys.RefreshToken);
public async Task SetRefreshToken(string accessToken) => await _localStorageService.SetAsync(_configuration.StorageKeys.AccessToken, accessToken);
public async Task DeleteRefreshToken() => await _localStorageService.DeleteAsync(_configuration.StorageKeys.RefreshToken);
private async Task GetValueAsync(string key)
{
try
{
ProtectedBrowserStorageResult result = await _localStorageService.GetAsync(key);
return result.Success ? result.Value : default;
}
catch (CryptographicException ex)
{
_logger.LogError(ex, "Browser storage error has occurred. Deleting value.");
await _localStorageService.DeleteAsync(key);
return default;
}
}
}
- Получите токен доступа из локального хранилища. Если токен не пуст и не истек, он возвращается.
- В противном случае получите токен обновления из локального хранилища. Если он пуст, верните пустую строку.
- Отправьте запрос с обоими токенами. Если токен доступа и обновления действителен (дата истечения срока действия токена доступа не проверена, токен обновления должен существовать в базе данных и быть назначен пользователю токена доступа), в ответ возвращается новый токен доступа. Конечная точка не требует заголовка авторизации.
- Если ответ успешен, сохраните токены и верните новый токен доступа. В противном случае верните пустую строку.
Код: Выделить всё
System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.
Код: Выделить всё
string? accessToken = await _tokensService.GetAccessToken();Второе решение
Я также пытался добавить заголовок с DelegatingHandler. Я создал новый класс:
Код: Выделить всё
public class AuthenticationHandler : DelegatingHandler
{
private readonly IAuthenticationService _authenticationService;
public AuthenticationHandler(IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
}
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string token = await _authenticationService.GetRawAccessTokenAsync();
if (!string.IsNullOrWhiteSpace(token))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
Код: Выделить всё
services.AddTransient();
services.AddRefitClient(settings).ConfigureHttpClient(x => x.BaseAddress = BaseUriFunc("authentication"));
services.AddRefitClient(settings).ConfigureHttpClient(x => x.BaseAddress = BaseUriFunc("genres")).AddHttpMessageHandler();
Конечно, я удалил [Headers("Authorization: Bearer")]< Атрибут /code> из клиентских методов.
Изменить 1
Я только что заметил, что проблемы возникают и вне OnAfterRenderAsync. Например, когда я попытался выполнить клиентский метод API в методе, назначенном событию @onclick кнопки.
Подробнее здесь: https://stackoverflow.com/questions/792 ... n-onafterr
Мобильная версия