请参阅下面的代码以解决此问题
我正在尝试找到处理ASP.NET Core 2.1中已过期的刷新令牌的最佳和最有效的方法。
让我解释一下。
我正在使用OAUTH2和OIDC来请求授权代码授权流程(或使用OIDC的混合流程)。此流/授权类型使我可以访问AccessToken和RefreshToken(授权代码,但这不适用于此问题)。
访问令牌和刷新令牌由ASP.NET核心存储,并且可以分别使用HttpContext.GetTokenAsync("access_token");
和HttpContext.GetTokenAsync("refresh_token");
进行检索。
我可以毫无问题地刷新access_token
。当refresh_token
以某种方式过期,撤销或无效时,问题就会发挥作用。
正确的流程是让用户再次登录并再次返回整个身份验证流程。然后应用程序获得一组新的令牌返回。
我的问题是如何以最好和最正确的方法实现这一目标。我决定编写一个自定义中间件,试图更新access_token
,如果它已经过期。然后,中间件将新令牌设置为HttpContext的AuthenticationProperties
,以便以后管道中的任何调用都可以使用它。
如果由于任何原因刷新令牌失败,我需要再次调用ChallengeAsync。我从中间件调用ChallengeAsync。
这是我遇到一些有趣的行为的地方。然而,大部分时间这都有效,有时候我会得到500个错误而没有关于什么是失败的有用信息。几乎看起来中间件试图从中间件调用ChallengeAsync有问题,也许另一个中间件也试图访问上下文。
我不太清楚发生了什么事。我不太确定这是否是适合这种逻辑的地方。也许我不应该在中间件中使用它,也许在其他地方。也许Polly for HttpClient是最好的地方。
我愿意接受任何想法。
感谢您的任何帮助,您可以提供。
代码解决方案对我有用
感谢Mickaël Derriey的帮助和指导(请务必在此解决方案中查看他的答案以获取更多信息)这是我提出的解决方案,它适用于我:
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
//check to see if user is authenticated first
if (context.Principal.Identity.IsAuthenticated)
{
//get the users tokens
var tokens = context.Properties.GetTokens();
var refreshToken = tokens.FirstOrDefault(t => t.Name == "refresh_token");
var accessToken = tokens.FirstOrDefault(t => t.Name == "access_token");
var exp = tokens.FirstOrDefault(t => t.Name == "expires_at");
var expires = DateTime.Parse(exp.Value);
//check to see if the token has expired
if (expires < DateTime.Now)
{
//token is expired, let's attempt to renew
var tokenEndpoint = "https://token.endpoint.server";
var tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
var tokenResponse = tokenClient.RequestRefreshTokenAsync(refreshToken.Value).Result;
//check for error while renewing - any error will trigger a new login.
if (tokenResponse.IsError)
{
//reject Principal
context.RejectPrincipal();
return Task.CompletedTask;
}
//set new token values
refreshToken.Value = tokenResponse.RefreshToken;
accessToken.Value = tokenResponse.AccessToken;
//set new expiration date
var newExpires = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
exp.Value = newExpires.ToString("o", CultureInfo.InvariantCulture);
//set tokens in auth properties
context.Properties.StoreTokens(tokens);
//trigger context to renew cookie with new token values
context.ShouldRenew = true;
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
};
访问令牌和刷新令牌由ASP.NET核心存储
我认为值得注意的是,令牌存储在cookie中,用于标识应用程序的用户。
现在这是我的看法,但我不认为自定义中间件是刷新令牌的正确位置。这样做的原因是,如果您成功刷新令牌,则需要替换现有令牌并将其以新cookie的形式发送回浏览器,该cookie将替换现有令牌。
这就是为什么我认为最相关的地方是ASP.NET Core正在读取cookie。每个认证机制都会暴露几个事件;对于cookie,有一个名为ValidatePrincipal
的cookie,在读取cookie并且已经成功地对其进行反序列化后,每个请求都会调用它。
public void ConfigureServices(ServiceCollection services)
{
services
.AddAuthentication()
.AddCookies(new CookieAuthenticationOptions
{
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
// context.Principal gives you access to the logged-in user
// context.Properties.GetTokens() gives you access to all the tokens
return Task.CompletedTask;
}
}
});
}
这种方法的好处是,如果你设法更新令牌并将其存储在AuthenticationProperties
中,context
类型的CookieValidatePrincipalContext
变量有一个名为ShouldRenew
的属性。将该属性设置为true
会指示中间件发出新cookie。
如果您无法续订令牌,或者您发现刷新令牌已过期并且您希望阻止用户继续前进,则该同一类具有RejectPrincipal
方法,该方法指示cookie中间件将请求视为匿名。
关于这一点的好处是,如果您的MVC应用程序仅允许经过身份验证的用户访问它,MVC将负责发出HTTP 401
响应,认证系统将捕获该响应并转变为挑战,并且用户将被重定向回到身份提供商。
我有一些代码可以显示这将如何在GitHub上的mderriey/TokenRenewal
存储库中运行。虽然意图不同,但它显示了如何使用这些事件的机制。