oauth2.0 ASP.NET核心中的AddOpenIdConnect和刷新标记

hrysbysz  于 2023-01-12  发布在  .NET
关注(0)|答案(4)|浏览(101)

我已经将AddOpenIdConnect添加到我的ASP.NET Core 3.1 Razor应用程序的ConfigureServices方法中。它工作得很好,直到令牌过期,然后我从IDP得到401个响应。
我看到过一个example,它展示了一种手动连接刷新令牌的方法。
但我很犹豫要不要这么做,微软的人没有考虑过刷新令牌似乎是超级不可能的。

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

o2gm4chl

o2gm4chl1#

这是我想到的。因为没有太多的例子,我可以找到如何做刷新令牌在ASP.NET核心与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;
});

此代码包含两个部分:

  • AddOpenIdConnect:这部分代码为应用程序设置OIDC。关键设置如下:
  • SignInScheme:这会让ASP.NET核心知道您要使用cookie来存储身份验证信息。
    • UseTokenLifetime:据我所知,这将cookie中的一个内部“expires_at”值设置为访问令牌的生命周期(不是实际的cookie过期时间,它停留在会话级别)。
    • SaveTokens:据我所知,这就是令牌保存在cookie中的原因。
  • OnValidatePrincipal:在验证Cookie时调用此部分。在此部分中,我们将检查访问令牌是否即将过期或过期。如果是,则将刷新令牌,并将更新的值存储在Cookie中。如果无法刷新令牌,则将用户重定向到登录屏幕。

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

  • clientId:OAuth2客户端ID。也称为客户端密钥、使用者密钥等。
  • clientSecret:OAuth2客户端机密。也称为使用者机密等。
  • oidcDiscoveryUrl:IDP的熟知配置文档的URL的基本部分。如果熟知配置文档位于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核心的新手。因此,我不能100%确定这些设置是否符合我的想法。这只是一段对我有用的代码,我想我可以分享它。它可能对你有用,也可能对你没用。
n9vozmp4

n9vozmp42#

据我所知,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.

relj7zay

relj7zay3#

我最近在一个.NET 7.0示例中实现了令牌刷新,在许多MS OIDC堆栈(包括旧堆栈)中,总是有一个刷新令牌和重写cookie的选项:Owin,.NET Core等。不过文档似乎很差,我不得不在aspnet源代码中挖掘,以找出cookie重写步骤。所以我想我应该添加到这个线程中,以防对未来的读者有用。

刷新令牌授予

首先发送基于标准的refresh token grant请求:

private async Task<JsonNode> RefreshTokens(HttpContext context)
{
    var tokenEndpoint = "https://login.example.com/oauth/v2/token";
    var clientId = "myclientid";
    var clientSecret = "myclientsecret";
    var refreshToken = await context.GetTokenAsync("refresh_token");

    var requestData = new[]
    {
        new KeyValuePair<string, string>("client_id", clientId),
        new KeyValuePair<string, string>("client_secret", clientSecret),
        new KeyValuePair<string, string>("grant_type", "refresh_token"),
        new KeyValuePair<string, string>("refresh_token", refreshToken),
    };

    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("accept", "application/json");
        
        var response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(requestData));
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        return JsonNode.Parse(json).AsObject();
    }
}

重写Cookie

然后重写cookie,这是通过使用一组新的令牌“登录”来完成的。更好的方法名称可能是类似“更新身份验证状态”的名称。如果您查看HTTP响应,您将看到一个更新的set-cookie标头,其中包含新的令牌。
请注意,在刷新令牌授予响应中,您可能会收到新的刷新令牌和新的ID令牌,也可能不会收到。如果没有收到,请提供现有值。

private async Task RewriteCookies(JsonNode tokens, HttpContext context)
{   
    var accessToken = tokens["access_token"]?.ToString();
    var refreshToken = tokens["refresh_token"]?.ToString();
    var idToken = tokens["id_token"]?.ToString();

    var newTokens = new List<AuthenticationToken>();
    newTokens.Add(new AuthenticationToken{ Name = "access_token", Value = accessToken });

    if (string.IsNullOrWhiteSpace(refreshToken))
    {
        refreshToken = await context.GetTokenAsync("refresh_token");
    }
    newTokens.Add(new AuthenticationToken{ Name = "refresh_token", Value = refreshToken });

    if (string.IsNullOrWhiteSpace(idToken))
    {
        idToken = await context.GetTokenAsync("id_token");
    }
    newTokens.Add(new AuthenticationToken{ Name = "id_token", Value = idToken });

    var properties = context.Features.Get<IAuthenticateResultFeature>().AuthenticateResult.Properties;
    properties.StoreTokens(newTokens);
    await context.SignInAsync(context.User, properties);
}

摘要

当你收到来自API的401响应时,能够刷新访问令牌是任何Web应用的基本功能。使用短期访问令牌,然后编写类似于上面的代码,以良好的可用性更新它们。
请注意,依赖到期时间并不完全可靠。在某些情况下,API令牌验证可能会由于基础架构事件而失败。API随后会为未到期的访问令牌返回401。Web应用应通过刷新处理此问题,然后重试API请求。

jucafojl

jucafojl4#

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

  • 饼干
  • 记忆
  • 数据库

您可以将令牌存储在cookie中,然后检查令牌的过期时间,并通过拦截cookie的验证事件来刷新令牌(如示例所示)。
但是AddOpenIdConnect没有逻辑来控制用户想要存储令牌的位置和自动实现令牌刷新。
您还可以尝试将中间件 Package 为ADAL .NET/MSAL .NET以提供缓存特性,然后您可以静默地获取/刷新令牌。

相关问题