在OIDC同时刷新访问令牌的多个请求



我有一些头抓手,可以在某些情况下更新刷新令牌,同时使用一个页面应用程序,可以同时进行多个API调用。我有一个带有以下内容的堆栈的水疗中心。

html/js spa-> MVC应用程序 -> webapi

我使用混合流,当用户登录页面时,我存储了id_token access_tokenrefresh_token在会话cookie中。

我使用的HttpClient有两个DelegatingHandlers与Web API交谈。一位委派处理者只需将访问令牌添加到授权标题中即可。另一个在此之前运行,并检查访问令牌上留下的寿命。如果访问令牌的时间有限,则使用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个请求,将创建3个新的刷新令牌,其中只有一个是有效的。由于应用程序的异步性质,我不能保证在会话中存储的刷新令牌实际上是最新的刷新令牌。下次我需要刷新刷新令牌时可能是无效的(而且通常是(。

到目前为止,我对可能解决方案的想法。

  • 在检查访问令牌或类似的地方检查访问令牌。但是,当不同的用户使用不同的会话(据我所知(,这有可能阻止它。如果我的MVC应用在多个实例中也无效。

  • 更改,因此使用后刷新令牌保持有效。因此,使用三个中的哪一个都没关系。

任何关于上述哪个更好的想法或任何人都有一个非常聪明的选择。

非常感谢!

当您的所有请求都来自同一水疗中心时,最好的是将它们同步在浏览器中并摆脱问题的服务器。每当您的客户端代码需要令牌时,请返回承诺。所有请求都有相同的承诺实例,因此它们都可以通过对服务器的唯一请求解决。

不幸的是,如果您通过本地API代理所有请求,并且永远不要将您的携带者传递到水疗中心,我的想法将行不通。

但是,如果您将刷新令牌保持绝对安全(永远不要将其发送到前面(,我看不到任何问题可以重复使用。在这种情况下,您可以按照此处描述的详细说明来打开滑动选项,以减少续订请求。

最新更新