From c32eee095425c142d99afab395fb867ccd73cded Mon Sep 17 00:00:00 2001 From: FrigaT Date: Wed, 22 Apr 2026 09:28:16 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20jwt=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=20=D0=B2=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= =?UTF-8?q?=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/AuthStateProvider.cs | 119 +++++++++++++++--- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/PlaylistShared.Pwa/Services/AuthStateProvider.cs b/PlaylistShared.Pwa/Services/AuthStateProvider.cs index c531880..2960cd5 100644 --- a/PlaylistShared.Pwa/Services/AuthStateProvider.cs +++ b/PlaylistShared.Pwa/Services/AuthStateProvider.cs @@ -12,6 +12,8 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable private readonly HttpClient _http; private Timer? _refreshTimer; private ClaimsPrincipal _currentUser = new(new ClaimsIdentity()); + private string? _currentToken; + private string? _currentRefreshToken; public AuthStateProvider(TokenStorage tokenStorage, ApiClient apiClient, HttpClient http) { @@ -26,14 +28,59 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable if (string.IsNullOrEmpty(token)) return new AuthenticationState(_currentUser); - var principal = ParseToken(token); - if (principal == null) - return new AuthenticationState(_currentUser); + var (isValid, principal) = await ValidateTokenAsync(token); + if (isValid && principal != null) + { + _currentUser = principal; + _currentToken = token; + _currentRefreshToken = refreshToken; + _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + ScheduleTokenRefresh(token, refreshToken); + return new AuthenticationState(principal); + } - _currentUser = principal; - _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - ScheduleTokenRefresh(token, refreshToken); - 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) @@ -41,6 +88,8 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable await _tokenStorage.SetTokensAsync(token, refreshToken); var principal = ParseToken(token); _currentUser = principal ?? new ClaimsPrincipal(new ClaimsIdentity()); + _currentToken = token; + _currentRefreshToken = refreshToken; _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } @@ -49,10 +98,51 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable { await _tokenStorage.ClearTokensAsync(); _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); + _currentToken = null; + _currentRefreshToken = null; _http.DefaultRequestHeaders.Authorization = null; + _refreshTimer?.Dispose(); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } + public async Task 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) { try @@ -62,7 +152,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable var identity = new ClaimsIdentity(jwt.Claims, "jwt"); return new ClaimsPrincipal(identity); } - catch (Exception ex) + catch { return null; } @@ -81,18 +171,7 @@ public class AuthStateProvider : AuthenticationStateProvider, IDisposable _refreshTimer?.Dispose(); _refreshTimer = new Timer(async _ => { - try - { - var newToken = await _apiClient.RefreshTokenAsync(refreshToken); - if (newToken != null) - await MarkUserAsAuthenticated(newToken.Token, newToken.RefreshToken); - else - await MarkUserAsLoggedOut(); - } - catch - { - await MarkUserAsLoggedOut(); - } + await TryRefreshTokenAsync(); }, null, (int)refreshTime.TotalMilliseconds, Timeout.Infinite); } }