diff --git a/TaxBaik.Web/Services/AuthService.cs b/TaxBaik.Web/Services/AuthService.cs index 372167d..8b420f1 100644 --- a/TaxBaik.Web/Services/AuthService.cs +++ b/TaxBaik.Web/Services/AuthService.cs @@ -14,7 +14,7 @@ public class AuthService private readonly ILogger _logger; private readonly string _jwtSecretKey; private readonly string? _passwordResetToken; - private readonly int _accessTokenExpirationMinutes = 15; // Access Token: 15분 + private readonly int _accessTokenExpirationMinutes = 60; // Access Token: 1시간 (사용성 향상) private readonly int _refreshTokenExpirationMinutes = 10080; // Refresh Token: 7일 public AuthService(IAdminUserRepository adminUserRepository, ILogger logger, IConfiguration configuration) diff --git a/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs b/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs index 27343cd..2e2424f 100644 --- a/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs +++ b/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs @@ -51,13 +51,33 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } + // 토큰이 만료되면 로그아웃 if (_tokenStore.IsAccessTokenExpired()) { - _logger.LogWarning("Access token 만료됨"); + _logger.LogWarning("Access token 만료됨 - 자동 로그아웃"); await LogoutAsync(); return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } + // 토큰이 5분 이내로 만료되면 자동 갱신 시도 (사용자 경험 향상) + if (!string.IsNullOrEmpty(_tokenStore.RefreshToken) && ShouldRefreshToken()) + { + _logger.LogInformation("토큰 만료 5분 전 - 자동 갱신 시작"); + var newTokenPair = await _authService.RefreshAccessTokenAsync(_tokenStore.RefreshToken); + if (newTokenPair != null) + { + await LoginAsync(newTokenPair.AccessToken, newTokenPair.RefreshToken, newTokenPair.ExpiresIn); + _logger.LogInformation("토큰 자동 갱신 성공"); + accessToken = newTokenPair.AccessToken; + } + else + { + _logger.LogWarning("토큰 자동 갱신 실패 - 로그아웃"); + await LogoutAsync(); + return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); + } + } + var principal = _authService.ValidateToken(accessToken); if (principal == null) { @@ -91,6 +111,25 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } + private bool ShouldRefreshToken() + { + // 토큰이 5분 이내로 만료되면 갱신 (300초 = 5분) + if (_tokenStore.TokenExpiryTicks <= 0) + return false; + + const int refreshThresholdSeconds = 300; + try + { + var expiryTime = new DateTime((long)_tokenStore.TokenExpiryTicks, DateTimeKind.Utc); + var timeUntilExpiry = expiryTime - DateTime.UtcNow; + return timeUntilExpiry.TotalSeconds <= refreshThresholdSeconds && timeUntilExpiry.TotalSeconds > 0; + } + catch + { + return false; + } + } + public async Task LogoutAsync() { // TokenStore 초기화