求助:IdentityServer4访问令牌自动更新配置问题
实现IdentityServer4访问令牌自动更新的完整配置指南
嘿,我来帮你搞定IdentityServer4中访问令牌自动更新的配置!你已经完成了基础的API和MVC客户端配置,接下来只需要补上几个关键步骤就能实现自动刷新了。核心逻辑是利用**刷新令牌(Refresh Token)**来在访问令牌过期前/过期后获取新的访问令牌,下面是分步实现方案:
1. 先确保IdentityServer的客户端配置允许刷新令牌
首先要在IdentityServer的客户端配置中开启离线访问权限,这样客户端才能获取到刷新令牌。在你的Config.cs的Clients数组里添加/修改对应客户端配置:
new Client { ClientId = "mvc_client", // 你的MVC客户端ID ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Code, // 用授权码模式 ClientSecrets = { new Secret("your_client_secret".Sha256()) }, // 替换成你的客户端密钥 RedirectUris = { "http://localhost:你的MVC端口/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:你的MVC端口/signout-callback-oidc" }, AllowedScopes = { "openid", "profile", "api1" }, // 包含你的API scope AllowOfflineAccess = true, // 关键:允许客户端获取刷新令牌 RefreshTokenUsage = TokenUsage.ReUse, // 刷新令牌可重复使用(也可设为OneTimeOnly) RefreshTokenExpiration = TokenExpiration.Sliding, // 滑动过期:每次刷新都延长有效期 AbsoluteRefreshTokenLifetime = 2592000, // 刷新令牌绝对过期时间(30天,单位秒) SlidingRefreshTokenLifetime = 1296000 // 滑动过期窗口(15天,单位秒) }
2. 完善MVC客户端的Authentication配置
你已经写了部分配置,现在补全OpenID Connect的完整配置,重点是开启令牌保存和请求离线访问权限:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") // 用Cookie保存用户会话 .AddOpenIdConnect("oidc", options => { options.Authority = "http://localhost:5100"; options.RequireHttpsMetadata = false; options.ClientId = "mvc_client"; // 对应IdentityServer中的客户端ID options.ClientSecret = "your_client_secret"; // 对应客户端密钥 options.ResponseType = "code"; // 授权码模式 options.SaveTokens = true; // 关键:将访问令牌、刷新令牌保存到Cookie中 options.Scope.Add("api1"); // 请求你的API权限 options.Scope.Add("offline_access"); // 必须添加这个scope才能获取刷新令牌 options.GetClaimsFromUserInfoEndpoint = true; // 可选:添加自定义事件处理 options.Events = new OpenIdConnectEvents { OnAuthenticationFailed = ctx => { ctx.Response.Redirect("/Home/Error"); ctx.HandleResponse(); return Task.CompletedTask; } }; });
3. 实现自动刷新令牌的逻辑
ASP.NET Core默认不会自动刷新令牌,需要我们手动实现刷新逻辑,这里推荐两种常用方案:
方案一:用中间件提前刷新即将过期的令牌
创建一个中间件,在每次请求时检查令牌是否即将过期,若过期则自动刷新:
public class TokenRefreshMiddleware { private readonly RequestDelegate _next; private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; public TokenRefreshMiddleware(RequestDelegate next, IHttpClientFactory httpClientFactory, IConfiguration configuration) { _next = next; _httpClientFactory = httpClientFactory; _configuration = configuration; } public async Task InvokeAsync(HttpContext context) { // 获取令牌过期时间 var expClaim = context.User.FindFirst("exp"); if (expClaim != null) { var expiresAt = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expClaim.Value)); // 若令牌5分钟内过期,尝试刷新 if (expiresAt < DateTimeOffset.UtcNow.AddMinutes(5)) { var refreshToken = await context.GetTokenAsync("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { var client = _httpClientFactory.CreateClient(); var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = $"{_configuration["IdentityServer:Authority"]}/connect/token", ClientId = _configuration["IdentityServer:ClientId"], ClientSecret = _configuration["IdentityServer:ClientSecret"], RefreshToken = refreshToken }); if (!tokenResponse.IsError) { // 更新Cookie中的令牌 var authProperties = context.AuthenticateAsync("Cookies").Result.Properties; authProperties.UpdateTokenValue("access_token", tokenResponse.AccessToken); authProperties.UpdateTokenValue("refresh_token", tokenResponse.RefreshToken); await context.SignInAsync("Cookies", context.User, authProperties); } else { // 刷新失败,跳转到登录页 context.Response.Redirect("/Account/Login"); return; } } } } await _next(context); } } // 在Startup.cs的Configure方法中注册中间件(放在UseAuthentication之后) app.UseAuthentication(); app.UseMiddleware<TokenRefreshMiddleware>(); app.UseAuthorization();
方案二:在API调用时被动刷新令牌
如果不想提前刷新,可以在API返回401(令牌过期)时再尝试刷新,这种方式适合用HttpClient调用API的场景:
public class TokenRefreshHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; public TokenRefreshHandler(IHttpContextAccessor httpContextAccessor, IHttpClientFactory httpClientFactory, IConfiguration configuration) { _httpContextAccessor = httpContextAccessor; _httpClientFactory = httpClientFactory; _configuration = configuration; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var context = _httpContextAccessor.HttpContext; // 添加当前访问令牌到请求头 var accessToken = await context.GetTokenAsync("access_token"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await base.SendAsync(request, cancellationToken); // 若返回401,尝试刷新令牌并重发请求 if (response.StatusCode == HttpStatusCode.Unauthorized) { var refreshToken = await context.GetTokenAsync("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { var client = _httpClientFactory.CreateClient(); var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = $"{_configuration["IdentityServer:Authority"]}/connect/token", ClientId = _configuration["IdentityServer:ClientId"], ClientSecret = _configuration["IdentityServer:ClientSecret"], RefreshToken = refreshToken }); if (!tokenResponse.IsError) { // 更新令牌 var authProperties = (await context.AuthenticateAsync("Cookies")).Properties; authProperties.UpdateTokenValue("access_token", tokenResponse.AccessToken); authProperties.UpdateTokenValue("refresh_token", tokenResponse.RefreshToken); await context.SignInAsync("Cookies", context.User, authProperties); // 重新发送请求 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); response = await base.SendAsync(request, cancellationToken); } else { context.Response.Redirect("/Account/Login"); } } else { context.Response.Redirect("/Account/Login"); } } return response; } } // 注册Handler到HttpClient services.AddHttpClient("apiClient", client => { client.BaseAddress = new Uri("http://localhost:你的API端口/"); }) .AddHttpMessageHandler<TokenRefreshHandler>(); // 别忘了注册IHttpContextAccessor services.AddHttpContextAccessor();
4. API端配置补充(可选)
你的API配置已经基本正确,这里可以补充令牌验证的细节,确保令牌有效期被正确验证:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5100"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; options.TokenValidationParameters = new TokenValidationParameters { ValidateLifetime = true, // 开启令牌有效期验证 ClockSkew = TimeSpan.FromMinutes(5) // 允许的时间偏移(避免服务器时间差导致的验证失败) }; });
内容的提问来源于stack exchange,提问作者paranamix2
相关产品推荐
相关产品推荐

