如何在OpenIddict的refresh_token授权中复用续期现有刷新令牌?
自定义OpenIddict 6.3.0 + ASP.NET Core 8的Refresh Token授权行为
目标
使用refresh_token授权时:
- 复用现有刷新令牌,仅延长其过期时间
- 发行新的访问令牌
- 禁止OpenIddict每次生成新的刷新令牌
问题
调用SignIn(principal, scheme)时,OpenIddict始终:
- ✅ 生成新的访问令牌(符合预期)
- ❌ 生成新的刷新令牌(不符合预期)
已尝试的操作:
- 设置
DisableRollingRefreshTokens = true - 未调用
SetRefreshTokenLifetime或SetTokenType("refresh_token") - 在
ProcessSignInContext中使用自定义处理器
无论采取何种操作,SignIn()都会生成新的刷新令牌,无法实现复用并续期旧令牌的需求。
环境配置
- OpenIddict 6.3.0
- ASP.NET Core 8
需要的解决方案
- 如何完全阻止OpenIddict在refresh_token授权的每次
SignIn()时生成新的刷新令牌? - 是否有推荐的方法在不依赖
SignIn()的情况下手动控制刷新令牌的生命周期? - 如何确保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。
步骤如下:
- 验证原始refresh_token的有效性
- 更新该令牌的
ExpiresAt属性到目标时间 - 使用OpenIddict令牌生成器手动创建新的access_token
- 构建标准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




