SEE Below for code that solved this issue
我正在尝试找到处理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是最好的地方 .
我愿意接受任何想法 .
感谢您的任何帮助,您可以提供 .
Code Solution that worked for me
感谢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;
}
};
1 回答
我认为值得注意的是,令牌存储在cookie中,用于标识应用程序的用户 .
现在这是我的看法,但我不认为自定义中间件是刷新令牌的正确位置 . 这样做的原因是,如果您成功刷新令牌,则需要替换现有令牌并将其以新cookie的形式发送回浏览器,该cookie将替换现有令牌 .
这就是为什么我认为最相关的地方是ASP.NET Core读取cookie时 . 每个认证机制都会暴露几个事件;对于cookie,有一个名为
ValidatePrincipal
的语句,在读取cookie并且已成功从其中反序列化后,每个请求都会调用它 .这种方法的好处是,如果您设法更新令牌并将其存储在
AuthenticationProperties
中,CookieValidatePrincipalContext
的context
变量具有一个名为ShouldRenew的属性 . 将该属性设置为true
指示中间件发出新cookie .如果您无法续订令牌或者您发现刷新令牌已过期并且您希望阻止用户继续前进,那么该类具有RejectPrincipal方法,该方法指示cookie中间件将请求视为匿名 .
关于这一点的好处是,如果您的MVC应用程序仅允许经过身份验证的用户访问它,MVC将负责发出身份验证系统将捕获的
HTTP 401
响应并转变为挑战,并且用户将被重定向回身份提供商 .我有一些代码显示了这将如何在GitHub上的mderriey/TokenRenewal存储库中工作 . 虽然意图不同,但它显示了如何使用这些事件的机制 .