You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在OpenIddict的refresh_token授权中复用续期现有刷新令牌?

自定义OpenIddict 6.3.0 + ASP.NET Core 8的Refresh Token授权行为

目标

使用refresh_token授权时:

  • 复用现有刷新令牌,仅延长其过期时间
  • 发行新的访问令牌
  • 禁止OpenIddict每次生成新的刷新令牌

问题

调用SignIn(principal, scheme)时,OpenIddict始终:

  • ✅ 生成新的访问令牌(符合预期)
  • ❌ 生成新的刷新令牌(不符合预期)

已尝试的操作:

  • 设置DisableRollingRefreshTokens = true
  • 未调用SetRefreshTokenLifetimeSetTokenType("refresh_token")
  • ProcessSignInContext中使用自定义处理器

无论采取何种操作,SignIn()都会生成新的刷新令牌,无法实现复用并续期旧令牌的需求。

环境配置

  • OpenIddict 6.3.0
  • ASP.NET Core 8

需要的解决方案

  1. 如何完全阻止OpenIddict在refresh_token授权的每次SignIn()时生成新的刷新令牌?
  2. 是否有推荐的方法在不依赖SignIn()的情况下手动控制刷新令牌的生命周期?
  3. 如何确保OpenIddict在处理响应时不会覆盖我手动创建的令牌?

解决方案

1. 阻止OpenIddict在SignIn()时生成新刷新令牌

DisableRollingRefreshTokens = true确实是禁用滚动刷新令牌的配置,但它生效的前提是你要在刷新流程中保留原始刷新令牌的标识,并明确告知OpenIddict复用该令牌。

在refresh_token授权处理逻辑中,需要:

  • 从请求中获取原始refresh_token
  • 查询OpenIddict令牌存储,找到对应的令牌实体
  • 在调用SignIn()前,将原始刷新令牌ID以OpenIddictConstants.Claims.RefreshTokenId为Claim类型附加到ClaimsPrincipal中

示例代码:

// 在refresh_token授权处理方法内
var refreshToken = await _openIddictTokenManager.FindByIdAsync(request.RefreshToken);
if (refreshToken == null)
{
    return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

// 生成用于新access_token的ClaimsPrincipal
var principal = await _openIddictUserManager.CreateUserPrincipalAsync(refreshToken.Subject);

// 附加原始刷新令牌ID,告知OpenIddict复用该令牌
principal.SetClaim(OpenIddictConstants.Claims.RefreshTokenId, refreshToken.Id);

// 调用SignIn,此时OpenIddict会复用现有刷新令牌并续期(需配置好生命周期)
await SignInAsync(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

同时确保OpenIddict服务器配置中正确启用禁用滚动刷新:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.DisableRollingRefreshTokens = true;
        // 其他服务器配置...
    });

2. 不依赖SignIn()手动控制刷新令牌生命周期

如果想完全绕过SignIn()的自动令牌生成逻辑,可以直接通过OpenIddict的令牌管理器更新现有刷新令牌的过期时间,再手动构建响应返回新的access_token。

步骤如下:

  1. 验证原始refresh_token的有效性
  2. 更新该令牌的ExpiresAt属性到目标时间
  3. 使用OpenIddict令牌生成器手动创建新的access_token
  4. 构建标准OAuth 2.0响应,返回新access_token和原refresh_token

示例代码:

// 验证原始refresh_token有效性
var refreshToken = await _openIddictTokenManager.FindByIdAsync(request.RefreshToken);
if (refreshToken == null || refreshToken.IsExpired())
{
    return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

// 续期刷新令牌(示例:延长30天)
refreshToken.ExpiresAt = DateTimeOffset.UtcNow.AddDays(30);
await _openIddictTokenManager.UpdateAsync(refreshToken);

// 手动生成新的access_token
var principal = await _openIddictUserManager.CreateUserPrincipalAsync(refreshToken.Subject);
var accessToken = await _openIddictTokenManager.CreateAsync(
    new OpenIddictTokenDescriptor
    {
        Subject = refreshToken.Subject,
        Type = OpenIddictConstants.Tokens.AccessToken,
        ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(15),
        Claims = principal.Claims.ToDictionary(c => c.Type, c => (object)c.Value)
    });

// 构建响应返回
return Ok(new OpenIddictResponse
{
    AccessToken = await _openIddictTokenManager.GetAsync(accessToken, OpenIddictConstants.Properties.Token),
    TokenType = OpenIddictConstants.TokenTypes.Bearer,
    ExpiresIn = (long)(accessToken.ExpiresAt - DateTimeOffset.UtcNow).TotalSeconds,
    RefreshToken = await _openIddictTokenManager.GetAsync(refreshToken, OpenIddictConstants.Properties.Token)
});

3. 防止OpenIddict覆盖手动创建的令牌

如果手动构建了响应,需要跳过OpenIddict的自动响应生成逻辑:可以直接返回自定义的OkResult而不调用SignIn(),或者在ProcessSignInContext事件中设置HandleResponse = true阻止OpenIddict继续处理。

若必须使用SignIn()但要自定义令牌,可以在ProcessSignInContext中替换或清除自动生成的令牌:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.AddEventHandler<OpenIddictServerEvents.ProcessSignInContext>(builder =>
        {
            builder.UseInlineHandler(async context =>
            {
                // 清除OpenIddict自动生成的刷新令牌
                context.RefreshToken = null;

                // 手动设置要复用的刷新令牌
                var refreshTokenId = context.Principal.FindFirst(OpenIddictConstants.Claims.RefreshTokenId)?.Value;
                if (!string.IsNullOrEmpty(refreshTokenId))
                {
                    var refreshToken = await _openIddictTokenManager.FindByIdAsync(refreshTokenId);
                    context.RefreshToken = await _openIddictTokenManager.GetAsync(refreshToken, OpenIddictConstants.Properties.Token);
                }

                return default;
            });
        });
    });

通过清除自动生成的令牌并直接设置自定义令牌值,即可避免OpenIddict覆盖手动配置的内容。


内容的提问来源于stack exchange,提问作者Suhail Keyjani

火山引擎 最新活动