我正在尝试找到处理在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已过期,则尝试更新access_token。然后,中间件将新令牌设置到HttpContext的AuthenticationProperties中,以便稍后在管道中使用。
如果由于任何原因刷新令牌失败,则需要再次调用ChallengeAsync。我从中间件中调用ChallengeAsync。
这里我遇到了一些有趣的行为。大部分时间它是有效的,但是有时我会得到500错误,没有有用的信息说明哪里出错了。看起来中间件试图从中间件调用ChallengeAsync存在问题,并且可能另一个中间件也在尝试访问上下文。
我不太确定发生了什么。我不确定这是否是放置此逻辑的正确位置。也许我不应该将其放在中间件中,而应该放在其他地方。也许针对HttpClient的Polly是最好的地方。
我对任何想法都持开放态度。
感谢您提供的任何帮助。
我使用的代码解决方案。
感谢Mickaël Derriey提供的帮助和指导(请确保查看他的答案以获取更多有关此解决方案上下文的信息)。这是我想出来的解决方案,对我来说有效:
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
//check to see if user is authenticated first
if (context.Principal.Identity.IsAuthenticated)
{
//get the user's 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;
}
};