Blazor Web App集成Duende IdentityServer自动登出问题求助
问题分析与解决方案
你的核心问题是服务器因闲置回收后,refresh token丢失导致无法自动刷新身份,结合现有配置,以下是关键遗漏点和修复方案:
1. 开启SaveTokens持久化Refresh Token
在Blazor Server的OpenIdConnect配置中,options.SaveTokens = false会导致IDP返回的refresh token无法存储到认证Cookie中,没有持久化的token,服务器回收后凭证直接丢失。
修改ConfigureAuthentication中的OpenIdConnect配置:
options.SaveTokens = true; // 改为true,将access/refresh token保存到Cookie
2. 配置Cookie认证的过期与滑动刷新
Blazor Server依赖Cookie维护会话,默认配置可能存在过期时间过短的问题。需显式配置Cookie过期时间,匹配access token生命周期,并开启滑动刷新延长会话:
builder.Services.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.ExpireTimeSpan = TimeSpan.FromHours(2); // 和access token生命周期保持一致 options.SlidingExpiration = true; // 闲置时自动延长Cookie过期时间 options.Cookie.MaxAge = options.ExpireTimeSpan; // 同步Cookie最大存活时间 });
3. 替换内存缓存为持久化分布式缓存
当前使用的AddDistributedMemoryCache()是内存级缓存,服务器进程回收后缓存的token会全部丢失。需改用持久化分布式缓存(如Redis、SQL Server缓存):
// 替换AddDistributedMemoryCache()为Redis缓存配置示例 builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); options.InstanceName = "BlazorApp-"; });
4. 完善PersistentAuthenticationStateProvider的token持久化
你的Blazor Client端PersistentAuthenticationStateProvider仅恢复了用户Claims信息,未处理token持久化。服务器回收后,客户端无法获取refresh token,导致无法重新获取凭证。
步骤1:扩展UserInfo类包含token字段
public class UserInfo { // 原有Claims相关字段... public string? AccessToken { get; set; } public string? RefreshToken { get; set; } public DateTimeOffset? AccessTokenExpiresAt { get; set; } public static UserInfo FromClaimsPrincipalAndTokens(ClaimsPrincipal principal, AuthenticationProperties properties) { var userInfo = FromClaimsPrincipal(principal); userInfo.AccessToken = properties.GetTokenValue("access_token"); userInfo.RefreshToken = properties.GetTokenValue("refresh_token"); userInfo.AccessTokenExpiresAt = properties.GetTokenValue("expires_at") != null ? DateTimeOffset.Parse(properties.GetTokenValue("expires_at")!) : null; return userInfo; } }
步骤2:修改Server端OnPersistingAsync方法
private async Task OnPersistingAsync() { var authenticationState = await GetAuthenticationStateAsync(); var principal = authenticationState.User; if (principal.Identity?.IsAuthenticated == true) { var authProperties = await _httpClient.GetAuthenticationPropertiesAsync(); persistentComponentState.PersistAsJson(nameof(UserInfo), UserInfo.FromClaimsPrincipalAndTokens(principal, authProperties)); } }
5. 检查Duende IDP的Refresh Token配置
确保IDP客户端的Refresh Token生命周期足够长,且开启滑动过期:
new Client { // 原有配置... RefreshTokenLifetime = 2592000, // 30天,按需调整 SlidingRefreshTokenLifetime = 1296000, // 15天,滑动刷新窗口 RefreshTokenUsage = TokenUsage.ReUse, // 或OneTimeOnly,按需选择 RefreshTokenExpiration = TokenExpiration.Sliding // 开启滑动过期 }
6. 验证AccessTokenManagement的配置
确保AddOpenIdConnectAccessTokenManagement()配置正确,API请求时使用ITokenClient自动刷新token:
public class AppUsersApiService { private readonly HttpClient _httpClient; private readonly ITokenClient _tokenClient; public AppUsersApiService(HttpClient httpClient, ITokenClient tokenClient) { _httpClient = httpClient; _tokenClient = tokenClient; } public async Task<User> Get() { var token = await _tokenClient.GetAccessTokenAsync("testapi"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); return await _httpClient.GetFromJsonAsync<User>("api/users/me"); } }
内容的提问来源于stack exchange,提问作者Kman
相关产品推荐
相关产品推荐

