Добавлено обновление jwt при входи в систему
This commit is contained in:
@@ -12,6 +12,8 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable
|
|||||||
private readonly HttpClient _http;
|
private readonly HttpClient _http;
|
||||||
private Timer? _refreshTimer;
|
private Timer? _refreshTimer;
|
||||||
private ClaimsPrincipal _currentUser = new(new ClaimsIdentity());
|
private ClaimsPrincipal _currentUser = new(new ClaimsIdentity());
|
||||||
|
private string? _currentToken;
|
||||||
|
private string? _currentRefreshToken;
|
||||||
|
|
||||||
public AuthStateProvider(TokenStorage tokenStorage, ApiClient apiClient, HttpClient http)
|
public AuthStateProvider(TokenStorage tokenStorage, ApiClient apiClient, HttpClient http)
|
||||||
{
|
{
|
||||||
@@ -26,21 +28,68 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable
|
|||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
return new AuthenticationState(_currentUser);
|
return new AuthenticationState(_currentUser);
|
||||||
|
|
||||||
var principal = ParseToken(token);
|
var (isValid, principal) = await ValidateTokenAsync(token);
|
||||||
if (principal == null)
|
if (isValid && principal != null)
|
||||||
return new AuthenticationState(_currentUser);
|
{
|
||||||
|
|
||||||
_currentUser = principal;
|
_currentUser = principal;
|
||||||
|
_currentToken = token;
|
||||||
|
_currentRefreshToken = refreshToken;
|
||||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
ScheduleTokenRefresh(token, refreshToken);
|
ScheduleTokenRefresh(token, refreshToken);
|
||||||
return new AuthenticationState(principal);
|
return new AuthenticationState(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Токен невалиден – пробуем обновить
|
||||||
|
if (!string.IsNullOrEmpty(refreshToken))
|
||||||
|
{
|
||||||
|
var newTokenResponse = await _apiClient.RefreshTokenAsync(refreshToken);
|
||||||
|
if (newTokenResponse != null && !string.IsNullOrEmpty(newTokenResponse.Token))
|
||||||
|
{
|
||||||
|
await MarkUserAsAuthenticated(newTokenResponse.Token, newTokenResponse.RefreshToken);
|
||||||
|
// После MarkUserAsAuthenticated состояние обновится через NotifyAuthenticationStateChanged,
|
||||||
|
// но текущий вызов всё равно должен вернуть нового пользователя
|
||||||
|
var (newIsValid, newPrincipal) = await ValidateTokenAsync(newTokenResponse.Token);
|
||||||
|
if (newIsValid && newPrincipal != null)
|
||||||
|
return new AuthenticationState(newPrincipal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Всё плохо — логаут
|
||||||
|
await MarkUserAsLoggedOut();
|
||||||
|
return new AuthenticationState(_currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательный метод проверки валидности токена (включая срок)
|
||||||
|
private async Task<(bool IsValid, ClaimsPrincipal? Principal)> ValidateTokenAsync(string token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jwt = handler.ReadJwtToken(token);
|
||||||
|
|
||||||
|
// Проверяем, не истёк ли токен
|
||||||
|
if (jwt.ValidTo < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var identity = new ClaimsIdentity(jwt.Claims, "jwt");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
return (true, principal);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task MarkUserAsAuthenticated(string token, string refreshToken)
|
public async Task MarkUserAsAuthenticated(string token, string refreshToken)
|
||||||
{
|
{
|
||||||
await _tokenStorage.SetTokensAsync(token, refreshToken);
|
await _tokenStorage.SetTokensAsync(token, refreshToken);
|
||||||
var principal = ParseToken(token);
|
var principal = ParseToken(token);
|
||||||
_currentUser = principal ?? new ClaimsPrincipal(new ClaimsIdentity());
|
_currentUser = principal ?? new ClaimsPrincipal(new ClaimsIdentity());
|
||||||
|
_currentToken = token;
|
||||||
|
_currentRefreshToken = refreshToken;
|
||||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||||
}
|
}
|
||||||
@@ -49,10 +98,51 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable
|
|||||||
{
|
{
|
||||||
await _tokenStorage.ClearTokensAsync();
|
await _tokenStorage.ClearTokensAsync();
|
||||||
_currentUser = new ClaimsPrincipal(new ClaimsIdentity());
|
_currentUser = new ClaimsPrincipal(new ClaimsIdentity());
|
||||||
|
_currentToken = null;
|
||||||
|
_currentRefreshToken = null;
|
||||||
_http.DefaultRequestHeaders.Authorization = null;
|
_http.DefaultRequestHeaders.Authorization = null;
|
||||||
|
_refreshTimer?.Dispose();
|
||||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string?> TryRefreshTokenAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_currentRefreshToken))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newToken = await _apiClient.RefreshTokenAsync(_currentRefreshToken);
|
||||||
|
if (newToken != null && !string.IsNullOrEmpty(newToken.Token))
|
||||||
|
{
|
||||||
|
await MarkUserAsAuthenticated(newToken.Token, newToken.RefreshToken);
|
||||||
|
return newToken.Token;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await MarkUserAsLoggedOut();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await MarkUserAsLoggedOut();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTokenExpiringSoon()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_currentToken))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jwt = handler.ReadJwtToken(_currentToken);
|
||||||
|
var expiresAt = jwt.ValidTo;
|
||||||
|
var timeToExpiry = expiresAt - DateTime.UtcNow;
|
||||||
|
return timeToExpiry < TimeSpan.FromMinutes(1);
|
||||||
|
}
|
||||||
|
|
||||||
private ClaimsPrincipal? ParseToken(string token)
|
private ClaimsPrincipal? ParseToken(string token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -62,7 +152,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable
|
|||||||
var identity = new ClaimsIdentity(jwt.Claims, "jwt");
|
var identity = new ClaimsIdentity(jwt.Claims, "jwt");
|
||||||
return new ClaimsPrincipal(identity);
|
return new ClaimsPrincipal(identity);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -81,18 +171,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable
|
|||||||
_refreshTimer?.Dispose();
|
_refreshTimer?.Dispose();
|
||||||
_refreshTimer = new Timer(async _ =>
|
_refreshTimer = new Timer(async _ =>
|
||||||
{
|
{
|
||||||
try
|
await TryRefreshTokenAsync();
|
||||||
{
|
|
||||||
var newToken = await _apiClient.RefreshTokenAsync(refreshToken);
|
|
||||||
if (newToken != null)
|
|
||||||
await MarkUserAsAuthenticated(newToken.Token, newToken.RefreshToken);
|
|
||||||
else
|
|
||||||
await MarkUserAsLoggedOut();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await MarkUserAsLoggedOut();
|
|
||||||
}
|
|
||||||
}, null, (int)refreshTime.TotalMilliseconds, Timeout.Infinite);
|
}, null, (int)refreshTime.TotalMilliseconds, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user