【问题标题】:Multiple Requests to Refresh Access Token At the Same Time In OIDC在 OIDC 中同时刷新访问令牌的多个请求
【发布时间】:2019-11-10 09:15:18
【问题描述】:

对于在特定情况下使用单个页面应用程序同时进行多个 api 调用来更新刷新令牌,我有点头疼。我有一个 SPA,它的堆栈由以下内容组成。

Html/JS SPA -> MVC 应用程序 -> WebAPI

我使用混合流,当用户登录页面时,我将id_tokenaccess_tokenrefresh_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 应用程序跨多个实例,它也不起作用。

  • 更改以使刷新令牌在使用后保持有效。所以使用这三个中的哪一个并不重要。

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

非常感谢!

【问题讨论】:

    标签: asp.net asp.net-core identityserver4 asp.net-core-identity openid-connect


    【解决方案1】:

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

    不幸的是,如果您通过本地 API 代理所有请求并且从不将您的不记名传递给 SPA,那么我的想法将行不通。

    但是,如果您确保刷新令牌绝对安全(切勿将其发送到前面),我看不出有任何问题可以使其可重用。在这种情况下,您可以打开滑动选项as excellently described here 以减少续订请求。

    【讨论】:

    • 非常感谢您的想法,但是正如您所说的那样,这需要将访问令牌提供给 SPA。出于安全原因,这不会发生,我使用的是混合流。
    • 当您处于如此高的安全级别时,您真的可以设置RefreshTokenUsage = ReUseOneTime 主要用于手机
    猜你喜欢
    • 2019-10-13
    • 2020-06-02
    • 1970-01-01
    • 2023-02-08
    • 2022-12-15
    • 2017-06-09
    • 2021-01-29
    • 2022-01-28
    • 2019-11-08
    相关资源
    最近更新 更多