【问题标题】:AddOpenIdConnect and Refresh Tokens in ASP.NET CoreASP.NET Core 中的 AddOpenIdConnect 和刷新令牌
【发布时间】:2020-07-06 14:20:31
【问题描述】:

我已将 AddOpenIdConnect 添加到我的 ASP.NET Core 3.1 Razor 应用程序的 ConfigureServices 方法中。在令牌过期之前它工作得很好,然后我从 IDP 收到 401 响应。

我看到了一个example,它展示了一种手动连接刷新令牌的方法。

但我犹豫不决。微软的人似乎不太可能没有考虑刷新令牌。

ASP.NET Core 3.1 是否有办法让刷新令牌自动更新访问令牌?

【问题讨论】:

标签: asp.net-core oauth-2.0 openid-connect asp.net-core-3.1 refresh-token


【解决方案1】:

据我所知,ASP.NET Core 3.1 中没有任何内置功能可以自动刷新访问令牌。但是我从 IdentityServer4 作者那里找到了这个方便的library,它将访问和刷新令牌存储在内存中(这可以被覆盖),并在您从库中请求访问令牌时自动刷新它们。

如何使用库:https://identitymodel.readthedocs.io/en/latest/aspnetcore/web.html.

NuGet 包:https://www.nuget.org/packages/IdentityModel.AspNetCore/

源代码:https://github.com/IdentityModel/IdentityModel.AspNetCore.

【讨论】:

    【解决方案2】:

    这是我想出的。由于我找不到太多关于如何在 ASP.NET Core 中使用 cookie 刷新令牌的示例,所以我想我会在这里发布。 (我在问题中链接的那个有问题。)

    这只是我的尝试。它尚未在任何生产环境中使用。此代码位于ConfigureServices 方法中。

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.Events = new CookieAuthenticationEvents
        {
            // After the auth cookie has been validated, this event is called.
            // In it we see if the access token is close to expiring.  If it is
            // then we use the refresh token to get a new access token and save them.
            // If the refresh token does not work for some reason then we redirect to 
            // the login screen.
            OnValidatePrincipal = async cookieCtx =>
            {
                var now = DateTimeOffset.UtcNow;
                var expiresAt = cookieCtx.Properties.GetTokenValue("expires_at");
                var accessTokenExpiration = DateTimeOffset.Parse(expiresAt);
                var timeRemaining = accessTokenExpiration.Subtract(now);
                // TODO: Get this from configuration with a fall back value.
                var refreshThresholdMinutes = 5;
                var refreshThreshold = TimeSpan.FromMinutes(refreshThresholdMinutes);
    
                if (timeRemaining < refreshThreshold)
                {
                    var refreshToken = cookieCtx.Properties.GetTokenValue("refresh_token");
                    // TODO: Get this HttpClient from a factory
                    var response = await new HttpClient().RequestRefreshTokenAsync(new RefreshTokenRequest
                    {
                        Address = tokenUrl,
                        ClientId = clientId,
                        ClientSecret = clientSecret,
                        RefreshToken = refreshToken
                    });
    
                    if (!response.IsError)
                    {
                        var expiresInSeconds = response.ExpiresIn;
                        var updatedExpiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresInSeconds);
                        cookieCtx.Properties.UpdateTokenValue("expires_at", updatedExpiresAt.ToString());
                        cookieCtx.Properties.UpdateTokenValue("access_token", response.AccessToken);
                        cookieCtx.Properties.UpdateTokenValue("refresh_token", response.RefreshToken);
                        
                        // Indicate to the cookie middleware that the cookie should be remade (since we have updated it)
                        cookieCtx.ShouldRenew = true;
                    }
                    else
                    {
                        cookieCtx.RejectPrincipal();
                        await cookieCtx.HttpContext.SignOutAsync();
                    }
                }
            }
        };
    })
    .AddOpenIdConnect(options =>
    {
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        
        options.Authority = oidcDiscoveryUrl;
        options.ClientId = clientId;
        options.ClientSecret = clientSecret;
    
        options.RequireHttpsMetadata = true;
        
        options.ResponseType = OidcConstants.ResponseTypes.Code;
        options.UsePkce = true;
        // This scope allows us to get roles in the service.
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");
    
        // This aligns the life of the cookie with the life of the token.
        // Note this is not the actual expiration of the cookie as seen by the browser.
        // It is an internal value stored in "expires_at".
        options.UseTokenLifetime = false;
        options.SaveTokens = true;
    });
    

    这段代码有两部分:

    1. AddOpenIdConnect:这部分代码为应用设置OIDC。这里的关键设置是:
      • SignInScheme:这让 ASP.NET Core 知道您想要使用 cookie 来存储您的身份验证信息。
      • *UseTokenLifetime:据我了解,这会将 cookie 中的内部“expires_at”值设置为访问令牌的生命周期。 (不是实际的 cookie 过期,它停留在会话级别。)
      • *SaveTokens:据我了解,这就是导致令牌保存在 cookie 中的原因。
    2. OnValidatePrincipal:当 cookie 被验证时调用此部分。在本节中,我们检查访问令牌是否接近或过期。如果是,那么它会被刷新并将更新的值存储在 cookie 中。如果无法刷新令牌,则会将用户重定向到登录屏幕。

    代码使用必须来自您的配置文件的这些值:

    • clientId:OAuth2 客户端 ID。也称为客户端密钥、消费者密钥等。
    • clientSecret:OAuth2 客户端密码。也称为消费者秘密等。
    • oidcDiscoveryUrl:您的 IDP 众所周知的配置文档的 URL 的基本部分。如果您的 Well Known Configuration 文档位于 https://youridp.domain.com/oauth2/oidcdiscovery/.well-known/openid-configuration,则该值将是 https://youridp.domain.com/oauth2/oidcdiscovery
    • tokenUrl:指向您的 IDP 令牌端点的 URL。例如:https:/youridp.domain.com/oauth2/token
    • refreshThresholdMinutes:如果您等到访问令牌非常接近到期,那么您将面临依赖访问令牌的调用失败的风险。 (如果距离过期还有 5 毫秒,那么它可能会过期,并且在您有机会刷新它之前调用失败。)此设置是您希望在过期前的分钟数,您希望认为访问令牌已准备好刷新。

    * 我是 ASP.NET Core 的新手。因此,我不能 100% 确定这些设置是否符合我的想法。这只是一些对我有用的代码,我想我会分享它。它可能适合您,也可能不适合您。

    【讨论】:

    • @Ergec - 我刚刚将令牌生命周期更新为假。后来的使用表明它设置为 true 会导致问题。
    • @Vaccano 非常感谢。
    • @Vaccano 谢谢!一直在努力解决这个问题,谢谢!
    【解决方案3】:

    AddOpenIdConnect 用于配置执行 OpenID Connect 协议以从您的身份提供者获取令牌的处理程序。但它不知道您要将令牌保存在哪里。它可以是以下任何一种:

    • 饼干
    • 内存
    • 数据库

    您可以将令牌存储在 cookie 中,然后检查令牌的过期时间并通过拦截 cookie 的验证事件来刷新令牌(如示例所示)。

    但是AddOpenIdConnect 没有逻辑来控制用户想要在哪里存储令牌并自动实现令牌刷新。

    您也可以尝试将中间件包装为 ADAL.NET/MSAL.NET 以提供缓存功能,然后您可以静默获取/刷新令牌。

    【讨论】:

      猜你喜欢
      • 2018-03-30
      • 1970-01-01
      • 2019-02-10
      • 2021-07-03
      • 2014-09-18
      • 2015-08-13
      • 2018-05-12
      • 2020-06-01
      • 2020-06-18
      相关资源
      最近更新 更多