2016-10-13 27 views
8

Skonfigurowałem serwer ASOS OpenIdConnect przy użyciu i podstawowej aplikacji mvc asp.net, która używa "Microsoft.AspNetCore.Authentication.OpenIdConnect": " 1.0.0 i "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". Przetestowałem przepływ pracy "Kod autoryzacji" i wszystko działa.Jak radzić sobie z tokenem dostępu wygasłego w rdzeniu asp.net za pomocą tokena odświeżania za pomocą OpenId Connect

Aplikacja internetowa klienta przetwarza uwierzytelnienie zgodnie z oczekiwaniami i tworzy plik cookie przechowywanie id_token, access_token i refresh_token

Jak wymusić Microsoft.AspNetCore.Authentication.OpenIdConnect, aby zażądać nowego token dostępu, gdy wygasa?

Aplikacja mvc core asp.net ignoruje wygasły access_token.

Chciałbym mieć openidconnect zobaczyć wygasły access_token, a następnie wykonać połączenie za pomocą odświeżania tokenu, aby uzyskać nowy access_token. Powinien również aktualizować wartości plików cookie. Jeśli żądanie odświeżania się nie powiedzie, spodziewam się, że openidconnect będzie "wylogowywać" plik cookie (usunąć lub coś podobnego).

app.UseCookieAuthentication(new CookieAuthenticationOptions 
     { 
      AutomaticAuthenticate = true, 
      AutomaticChallenge = true, 
      AuthenticationScheme = "Cookies" 
     }); 

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions 
     { 
      ClientId = "myClient", 
      ClientSecret = "secret_secret_secret", 
      PostLogoutRedirectUri = "http://localhost:27933/", 
      RequireHttpsMetadata = false, 
      GetClaimsFromUserInfoEndpoint = true, 
      SaveTokens = true, 
      ResponseType = OpenIdConnectResponseType.Code, 
      AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet, 
      Authority = http://localhost:27933, 
      MetadataAddress = "http://localhost:27933/connect/config", 
      Scope = { "email", "roles", "offline_access" }, 
     }); 

Odpowiedz

9

Wygląda na to, że nie ma programowania w uwierzytelnianiu openidconnect dla rdzenia asp.net w celu zarządzania access_token na serwerze po otrzymaniu.

Znalazłem, że mogę przechwycić zdarzenie sprawdzania poprawności plików cookie i sprawdzić, czy token dostępu wygasł. Jeśli tak, wykonaj ręczne wywołanie HTTP do punktu końcowego tokena za pomocą elementu grant_type = refresh_token.

Wywołując context.ShouldRenew = true; spowoduje to, że plik cookie zostanie zaktualizowany i odesłany do klienta w odpowiedzi.

Podałem podstawy tego, co zrobiłem i będę pracował nad zaktualizowaniem tej odpowiedzi, gdy wszystkie zostaną rozwiązane.

app.UseCookieAuthentication(new CookieAuthenticationOptions 
     { 
      AutomaticAuthenticate = true, 
      AutomaticChallenge = true, 
      AuthenticationScheme = "Cookies", 
      ExpireTimeSpan = new TimeSpan(0, 0, 20), 
      SlidingExpiration = false, 
      CookieName = "WebAuth", 
      Events = new CookieAuthenticationEvents() 
      { 
       OnValidatePrincipal = context => 
       { 
        if (context.Properties.Items.ContainsKey(".Token.expires_at")) 
        { 
         var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]); 
         if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues. 
         { 
          logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}"); 

          //TODO: send refresh token to ASOS. Update tokens in context.Properties.Items 
          //context.Properties.Items["Token.access_token"] = newToken; 
          context.ShouldRenew = true; 
         } 
        } 
        return Task.FromResult(0); 
       } 
      } 
     }); 
+0

Powinno zostać (wygasnąć> DateTime.Now) (wygasa> DateTime.UtcNow)? https://docs.asp.net/en/latest/security/authentication/cookie.html sugeruje użycie czasu utc. – Darxtar

+0

Nie, datetime.parse odczytuje wartość ".Token.expires_at" jako UTC i analizuje czas lokalny. ".Token.expires_at" 2016-10-19T16: 02: 39.0008091 + 00: 00 ". – longday

+0

OK, dziękuję za wyjaśnienie =) – Darxtar

1

należy włączyć generowanie refresh_token poprzez ustawienie w startup.cs:

  • wartości ustawienia do AuthorizationEndpointPath = "/ connect/autoryzować"; // potrzebne do refreshtoken
  • Ustawianie wartości na TokenEndpointPath = "/ connect/token"; // średnia Token końcowym

W token dostawcy, przed walidacji tokenu żądania na końcu metody HandleTokenrequest, upewnij się, że został ustawiony w trybie offline zakresu:

 // Call SetScopes with the list of scopes you want to grant 
     // (specify offline_access to issue a refresh token). 
     ticket.SetScopes(
      OpenIdConnectConstants.Scopes.Profile, 
      OpenIdConnectConstants.Scopes.OfflineAccess); 

Jeśli to ustawienie poprawnie, powinieneś otrzymać refresh_token, gdy logujesz się z hasłem grant_type.

Następnie od klienta należy wydać następujące żądanie (używam Aurelia):

refreshToken() { 
    let baseUrl = yourbaseUrl; 

    let data = "client_id=" + this.appState.clientId 
       + "&grant_type=refresh_token" 
       + "&refresh_token=myRefreshToken"; 

    return this.http.fetch(baseUrl + 'connect/token', { 
     method: 'post', 
     body : data, 
     headers: { 
      'Content-Type': 'application/x-www-form-urlencoded', 
      'Accept': 'application/json' 
     } 
    }); 
} 

i to jest to, upewnij się, że dostawca auth w HandleRequestToken nie próbuje manipulować wniosek, który jest typu refresh_token:

public override async Task HandleTokenRequest(HandleTokenRequestContext context) 
    { 
     if (context.Request.IsPasswordGrantType()) 
     { 
      // Password type request processing only 
      // code that shall not touch any refresh_token request 
     } 
     else if(!context.Request.IsRefreshTokenGrantType()) 
     { 
      context.Reject(
        error: OpenIdConnectConstants.Errors.InvalidGrant, 
        description: "Invalid grant type."); 
      return; 
     } 

     return; 
    } 

refresh_token powinien po prostu być w stanie przejść przez tą metodą i jest obsługiwany przez inny kawałek oprogramowania pośredniczącego, który obsługuje refresh_token.

Jeśli chcesz bardziej dogłębnej wiedzy na temat tego, co serwer uwierzytelniania robi, można rzucić okiem na kodzie OpenIdConnectServerHandler:

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs

po stronie klienta musi również być w stanie obsłużyć automatyczne odświeżanie tokenu, tutaj jest przykładem przechwytywacza http dla Angular 1.X, gdzie jeden obsługuje 401 reponses, odśwież żeton, a następnie ponowić żądanie:

'use strict'; 
app.factory('authInterceptorService', 
    ['$q', '$injector', '$location', 'localStorageService', 
    function ($q, $injector, $location, localStorageService) { 

    var authInterceptorServiceFactory = {}; 
    var $http; 

    var _request = function (config) { 

     config.headers = config.headers || {}; 

     var authData = localStorageService.get('authorizationData'); 
     if (authData) { 
      config.headers.Authorization = 'Bearer ' + authData.token; 
     } 

     return config; 
    }; 

    var _responseError = function (rejection) { 
     var deferred = $q.defer(); 
     if (rejection.status === 401) { 
      var authService = $injector.get('authService'); 
      console.log("calling authService.refreshToken()"); 
      authService.refreshToken().then(function (response) { 
       console.log("token refreshed, retrying to connect"); 
       _retryHttpRequest(rejection.config, deferred); 
      }, function() { 
       console.log("that didn't work, logging out."); 
       authService.logOut(); 

       $location.path('/login'); 
       deferred.reject(rejection); 
      }); 
     } else { 
      deferred.reject(rejection); 
     } 
     return deferred.promise; 
    }; 

    var _retryHttpRequest = function (config, deferred) { 
     console.log('autorefresh'); 
     $http = $http || $injector.get('$http'); 
     $http(config).then(function (response) { 
      deferred.resolve(response); 
     }, 
     function (response) { 
      deferred.reject(response); 
     }); 
    } 

    authInterceptorServiceFactory.request = _request; 
    authInterceptorServiceFactory.responseError = _responseError; 
    authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest; 

    return authInterceptorServiceFactory; 
}]); 

I tu jest przykładem właśnie zrobił dla Aurelia, tym razem zawinięte mojego klienta HTTP do programu obsługi http, który sprawdza, czy token wygasł lub nie. Jeśli wygasł, najpierw odświeży token, a następnie wykona żądanie. Wykorzystuje obietnicę utrzymania spójności interfejsu z usługami danych po stronie klienta. Ten moduł obsługi udostępnia ten sam interfejs, co klient pobierania aurelia.

import {inject} from 'aurelia-framework'; 
import {HttpClient} from 'aurelia-fetch-client'; 
import {AuthService} from './authService'; 

@inject(HttpClient, AuthService) 
export class HttpHandler { 

    constructor(httpClient, authService) { 
     this.http = httpClient; 
     this.authService = authService; 
    } 

    fetch(url, options){ 
     let _this = this; 
     if(this.authService.tokenExpired()){ 
      console.log("token expired"); 
      return new Promise(
       function(resolve, reject) { 
        console.log("refreshing"); 
        _this.authService.refreshToken() 
        .then(
         function (response) { 
          console.log("token refreshed"); 
         _this.http.fetch(url, options).then(
          function (success) { 
           console.log("call success", url); 
           resolve(success); 
          }, 
          function (error) { 
           console.log("call failed", url); 
           reject(error); 
          }); 
         }, function (error) { 
          console.log("token refresh failed"); 
          reject(error); 
        }); 
       } 
      ); 
     } 
     else { 
      // token is not expired, we return the promise from the fetch client 
      return this.http.fetch(url, options); 
     } 
    } 
} 

dla jQuery można wyglądać OAuth jquery:

https://github.com/esbenp/jquery-oauth

nadzieję, że to pomaga.

+0

W jaki sposób uzyskać stronę klienta, aby automatycznie przetworzyć nieważny token dostępu, żądając nowego tokena za pomocą refresh_token? Korzystam z biblioteki klienta "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0. Jeśli muszę ręcznie przetworzyć token odświeżania, jakie są najlepsze metody? Jak zaktualizować plik cookie klienta? – longday

+0

Implementacja zależy od framework frontend ... Używam tokenów OAuth 2, więc sam ustawię token w pliku cookie, zanim wyślę go do klienta ... Jeśli plik cookie ma taką samą nazwę, zostanie zastąpiony przez nowy. sposoby automatycznego odświeżania: 1) Zapobiegawczy, w którym sprawdzasz czas wygaśnięcia tokena i odświeżasz PRZED wykonaniem rzeczywistego żądania 2) Reaktywny: słuchasz stanów http w swoim kliencie, a jeśli otrzymujesz 401, odświeżasz Jeśli używasz Angular lub Aurelia, możesz skonfigurować przechwytywacz http – Darxtar

+0

Dodałem przykład do mojej odpowiedzi na kątową literę 1.X, mam nadzieję, że to pomoże .. – Darxtar