ASP.NET 核心中的 AddOpenIdConnect 和刷新令牌
AddOpenIdConnect and Refresh Tokens in ASP.NET Core
我已将 AddOpenIdConnect
添加到 ASP.NET Core 3.1 Razor 应用程序的 ConfigureServices
方法中。在令牌过期之前它一直有效,然后我从 IDP 收到 401 响应。
我看到 example 展示了一种手动连接刷新令牌的方法。
但是我很犹豫要不要那样做。微软的人似乎不太可能没有考虑刷新令牌。
ASP.NET Core 3.1 是否有办法让刷新令牌自动更新访问令牌?
AddOpenIdConnect
用于配置执行 OpenID Connect 协议的处理程序以从您的身份提供者获取令牌。但它不知道你想把令牌保存在哪里。它可以是以下任何一项:
- Cookie
- 内存
- 数据库
您可以将令牌存储在 cookie 中,然后检查令牌的过期时间并通过拦截 cookie 的验证事件来刷新令牌(如示例所示)。
但是AddOpenIdConnect
没有逻辑来控制用户想要存储令牌的位置并自动实现令牌刷新。
你也可以尝试把中间件包装成ADAL.NET/MSAL.NET来提供缓存功能,然后你就可以acquire/refresh默默地令牌了。
这是我想出的。由于我找不到很多关于如何在 ASP.NET Core 中使用 cookie 刷新令牌的示例,我想我会 post 在这里。 (我 link 在问题中的那个有问题。)
这只是我尝试让它工作的尝试。它尚未在任何生产环境中使用。此代码进入 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 Core 知道您想使用 cookie 来存储您的身份验证信息。
- *
UseTokenLifetime
:据我了解,这会将 cookie 中的内部“expires_at”值设置为访问令牌的生命周期。 (不是实际的 cookie 过期,停留在会话级别。)
- *
SaveTokens
:据我了解,这就是导致令牌保存在 cookie 中的原因。
OnValidatePrincipal
:当 cookie 被验证时调用此部分。在本节中,我们检查访问令牌是否接近或已过期。如果是,那么它会被刷新并将更新后的值存储在 cookie 中。如果无法刷新令牌,则用户将被重定向到登录屏幕。
代码使用的这些值必须来自您的配置文件:
clientId
:OAuth2 客户端 ID。也称为客户端密钥、消费者密钥等
clientSecret
:OAuth2 客户端机密。也称为消费者秘密等
oidcDiscoveryUrl
:URL 的基础部分到您的 IDP 的众所周知的配置文档。如果您的 Well Known Configuration 文档位于 https://youridp.domain.com/oauth2/oidcdiscovery/.well-known/openid-configuration
,则此值将为 https://youridp.domain.com/oauth2/oidcdiscovery
.
tokenUrl
: Url 到您的 IDP 的令牌端点。例如:https:/youridp.domain.com/oauth2/token
refreshThresholdMinutes
:如果您等到访问令牌非常接近到期,那么您 运行 依赖于访问令牌的调用失败的风险。 (如果距离过期还有 5 毫秒,那么在您有机会刷新它之前它可能会过期并导致调用失败。)此设置是您要考虑准备好刷新访问令牌的过期前分钟数。
* 我是 ASP.NET Core 的新手。因此,我不能 100% 确定这些设置是否符合我的想法。这只是一些对我有用的代码,我想我会分享它。它可能适合你,也可能不适合你。
据我所知,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.
我已将 AddOpenIdConnect
添加到 ASP.NET Core 3.1 Razor 应用程序的 ConfigureServices
方法中。在令牌过期之前它一直有效,然后我从 IDP 收到 401 响应。
我看到 example 展示了一种手动连接刷新令牌的方法。
但是我很犹豫要不要那样做。微软的人似乎不太可能没有考虑刷新令牌。
ASP.NET Core 3.1 是否有办法让刷新令牌自动更新访问令牌?
AddOpenIdConnect
用于配置执行 OpenID Connect 协议的处理程序以从您的身份提供者获取令牌。但它不知道你想把令牌保存在哪里。它可以是以下任何一项:
- Cookie
- 内存
- 数据库
您可以将令牌存储在 cookie 中,然后检查令牌的过期时间并通过拦截 cookie 的验证事件来刷新令牌(如示例所示)。
但是AddOpenIdConnect
没有逻辑来控制用户想要存储令牌的位置并自动实现令牌刷新。
你也可以尝试把中间件包装成ADAL.NET/MSAL.NET来提供缓存功能,然后你就可以acquire/refresh默默地令牌了。
这是我想出的。由于我找不到很多关于如何在 ASP.NET Core 中使用 cookie 刷新令牌的示例,我想我会 post 在这里。 (我 link 在问题中的那个有问题。)
这只是我尝试让它工作的尝试。它尚未在任何生产环境中使用。此代码进入 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 Core 知道您想使用 cookie 来存储您的身份验证信息。- *
UseTokenLifetime
:据我了解,这会将 cookie 中的内部“expires_at”值设置为访问令牌的生命周期。 (不是实际的 cookie 过期,停留在会话级别。) - *
SaveTokens
:据我了解,这就是导致令牌保存在 cookie 中的原因。
OnValidatePrincipal
:当 cookie 被验证时调用此部分。在本节中,我们检查访问令牌是否接近或已过期。如果是,那么它会被刷新并将更新后的值存储在 cookie 中。如果无法刷新令牌,则用户将被重定向到登录屏幕。
代码使用的这些值必须来自您的配置文件:
clientId
:OAuth2 客户端 ID。也称为客户端密钥、消费者密钥等clientSecret
:OAuth2 客户端机密。也称为消费者秘密等oidcDiscoveryUrl
:URL 的基础部分到您的 IDP 的众所周知的配置文档。如果您的 Well Known Configuration 文档位于https://youridp.domain.com/oauth2/oidcdiscovery/.well-known/openid-configuration
,则此值将为https://youridp.domain.com/oauth2/oidcdiscovery
.tokenUrl
: Url 到您的 IDP 的令牌端点。例如:https:/youridp.domain.com/oauth2/token
refreshThresholdMinutes
:如果您等到访问令牌非常接近到期,那么您 运行 依赖于访问令牌的调用失败的风险。 (如果距离过期还有 5 毫秒,那么在您有机会刷新它之前它可能会过期并导致调用失败。)此设置是您要考虑准备好刷新访问令牌的过期前分钟数。
* 我是 ASP.NET Core 的新手。因此,我不能 100% 确定这些设置是否符合我的想法。这只是一些对我有用的代码,我想我会分享它。它可能适合你,也可能不适合你。
据我所知,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.