asp.net - 在 OIDC 中同时刷新访问令牌的多个请求
问题描述
对于在特定情况下使用单个页面应用程序同时进行多个 api 调用来更新刷新令牌,我有点头疼。我有一个 SPA,它有一个包含以下内容的堆栈。
Html/JS SPA -> MVC 应用程序 -> WebAPI
我使用混合流,当用户登录页面时,我将id_token
theaccess_token
和 the存储refresh_token
在会话 cookie 中。
我使用一个HttpClient
有两个DelegatingHandlers
与 Web API 对话的。委托处理程序之一只是将访问令牌添加到 Authorization 标头。另一个在此之前运行并检查访问令牌上剩余的生命周期。如果访问令牌的剩余时间有限,则使用 refresh_token 获取新凭据并将它们保存回我的会话。
这是 OidcTokenRefreshHandler 的代码。
public class OidcTokenRefreshHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly OidcTokenRefreshHandlerParams _handlerParams;
public OidcTokenRefreshHandler(IHttpContextAccessor httpContextAccessor, OidcTokenRefreshHandlerParams handlerParams)
{
_httpContextAccessor = httpContextAccessor;
_handlerParams = handlerParams;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
var handler = new JwtSecurityTokenHandler();
var accessTokenObj = handler.ReadJwtToken(accessToken);
var expiry = accessTokenObj.ValidTo;
if (expiry - TimeSpan.FromMinutes(_handlerParams.AccessTokenThresholdTimeInMinutes) < DateTime.UtcNow )
{
await RefreshTokenAsync(cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private async Task RefreshTokenAsync(CancellationToken cancellationToken)
{
var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(_handlerParams.OidcAuthorityUrl, cancellationToken);
if (discoveryResponse.IsError)
{
throw new Exception(discoveryResponse.Error);
}
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = _handlerParams.OidcClientId,
ClientSecret = _handlerParams.OidcClientSecret,
RefreshToken = refreshToken
}, cancellationToken);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResponse.IdentityToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResponse.AccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResponse.RefreshToken
}
};
// Sign in the user with a new refresh_token and new access_token.
var info = await _httpContextAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.StoreTokens(tokens);
await _httpContextAccessor.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
问题是许多电话几乎同时打到这个。然后,所有这些调用将同时到达刷新端点。他们都将检索新的有效访问令牌,并且应用程序将继续工作。但是,如果同时发生 3 个请求,则将创建三个新的刷新令牌,其中只有一个有效。由于应用程序的异步特性,我不能保证存储在我的会话中的刷新令牌实际上是最新的刷新令牌。下次我需要刷新刷新令牌时可能是无效的(而且经常是)。
到目前为止,我对可能的解决方案的想法。
在使用 Mutex 或类似工具检查访问令牌时锁定。但是,当不同的用户在不同的会话中使用它时(据我所知),这有可能被阻止。如果我的 MVC 应用程序跨多个实例,它也不起作用。
更改以使刷新令牌在使用后保持有效。所以使用这三个中的哪一个并不重要。
任何关于上述哪一个更好的想法,或者有人有一个非常聪明的选择。
非常感谢!
解决方案
当您的所有请求都来自同一个 SPA 时,最好的办法是在浏览器中同步它们并摆脱服务器端的问题。每次您的客户端代码需要令牌时,都返回一个承诺。所有请求都使用相同的 Promise 实例,因此它们都可以通过对服务器的唯一请求来解决。
不幸的是,如果您通过本地 API 代理所有请求并且从不将您的承载传递给 SPA,那么我的想法将行不通。
但是,如果您确保刷新令牌绝对安全(永远不要将其发送到前面),我看不出有任何问题可以使其可重用。在这种情况下,您可以打开滑动选项,如此处所述,以执行更少的续订请求。
推荐阅读
- ios - Swift & Firebase:在 Firebase 中搜索/查找数据后执行 Segue
- docker - Docker Compose 端口映射:127.0.0.1 拒绝连接
- c# - 具有多个数据库配置的 SignalR
- php - 使用 Bootstrap 4 水平居中动态 div.card
- node.js - 我的应用程序中的表单值没有保存在 MongoDb 中
- javascript - 反应地图功能不渲染
- java - 当 modCount 在 java.util.ArrayList 中初始化时?
- laravel - laravel 更新方法忽略隐藏选择中的字段
- c# - 我在使用 c# 读取文件属性“文件类型”时遇到问题
- java - 实现 Parcelable 接口时如何读/写 java.math.BigInteger?