Asp.net-Core

在 ASP.NET Core 中處理過期的刷新令牌

  • September 4, 2018

有關解決此問題的程式碼,請參見下文

我正在嘗試找到處理在 ASP.NET Core 2.1 中過期的刷新令牌的最佳和最有效的方法。

讓我再解釋一下。

我正在使用 OAUTH2 和 OIDC 請求授權碼授權流(或帶有 OIDC 的混合流)。這種流/授權類型使我可以訪問 AccessToken 和 RefreshToken(授權碼也是如此,但這不適用於這個問題)。

訪問令牌和刷新令牌由 ASP.NET Core 儲存,可以分別使用HttpContext.GetTokenAsync("access_token");和檢索HttpContext.GetTokenAsync("refresh_token");

我可以刷新access_token沒有任何問題。當refresh_token以某種方式過期、撤銷或無效時,問題就會出現。

正確的流程是讓使用者登錄並再次返回整個身份驗證流程。然後應用程序會返回一組新的令牌。

我的問題是如何以最好和最正確的方法實現這一目標。我決定編寫一個自定義中間件來嘗試更新access_token它是否已過期。然後中間件將新令牌設置到AuthenticationPropertiesHttpContext 中,以便以後管道中的任何呼叫都可以使用它。

如果由於某種原因刷新令牌失敗,我需要再次呼叫 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;
   }
};

訪問令牌和刷新令牌由 ASP.NET 核心儲存

我認為重要的是要注意令牌儲存在向您的應用程序標識使用者的 cookie 中。

現在這是我的觀點,但我認為自定義中間件不是刷新令牌的正確位置。這樣做的原因是,如果您成功刷新令牌,則需要替換現有令牌並將其發送回瀏覽器,以新 cookie 的形式替換現有令牌。

這就是為什麼我認為最相關的地方是當 ASP.NET Core 讀取 cookie 時。每個身份驗證機制都會暴露幾個事件;對於 cookie,ValidatePrincipal在讀取 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;
               }
           }
       });
}

這種方法的好處是,如果您設法更新令牌並將其儲存在 中AuthenticationPropertiescontext則類型為 的變數CookieValidatePrincipalContext具有名為 的屬性ShouldRenew。將該屬性設置為true指示中間件發出新的 cookie。

如果您無法更新令牌,或者您發現刷新令牌已過期並且您想阻止使用者繼續前進,那麼同一類有一個RejectPrincipal方法可以指示 cookie 中間件將請求視為匿名請求。

這樣做的好處是,如果您的 MVC 應用程序只允許經過身份驗證的使用者訪問它,MVC 將負責發出HTTP 401響應,身份驗證系統將擷取並轉換為挑戰,並且使用者將被重定向回身份提供者。

我有一些程式碼顯示了這將如何mderriey/TokenRenewal在 GitHub 上的儲存庫中執行。雖然意圖不同,但它顯示瞭如何使用這些事件的機制。

引用自:https://stackoverflow.com/questions/52175302