You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:36:33