Używam szablonu Web Api 2, który jest dostarczany z programem Visual Studio 2013, zawiera oprogramowanie pośredniczące OWIN do uwierzytelniania użytkowników i tym podobne.
W OAuthAuthorizationServerOptions
zauważyłem, że serwer OAuth2 jest skonfigurowany do wydawania tokenów, które wygasają za 14 dni
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
To nie jest odpowiednie dla mojego najnowszego projektu. Chciałbym wręczyć krótkotrwałe bearer_tokens, które można odświeżyć za pomocą plikurefresh_token
Dużo szukałem w Google i nie mogę znaleźć nic pomocnego.
A więc tak daleko udało mi się dotrzeć. Dotarłem teraz do punktu „WTF do I teraz”.
Napisałem, RefreshTokenProvider
że implementuje się IAuthenticationTokenProvider
zgodnie z RefreshTokenProvider
właściwością w OAuthAuthorizationServerOptions
klasie:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid, context.Ticket);
// hash??
context.SetToken(guid);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
// Now in my Startup.Auth.cs
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider() // This is my test
};
Więc teraz, kiedy ktoś prosi o a bearer_token
, teraz wysyłam refresh_token
, co jest świetne.
Jak więc teraz użyć tego refresh_token, aby uzyskać nowy bearer_token
, prawdopodobnie muszę wysłać żądanie do mojego punktu końcowego tokenu z określonymi określonymi nagłówkami HTTP?
Po prostu głośno myślę, kiedy piszę ... Czy powinienem obsłużyć wygaśnięcie tokena refresh_token w moim SimpleRefreshTokenProvider
? W jaki sposób klient uzyskałby nowego refresh_token
?
Naprawdę przydałby mi się materiał do czytania / dokumentacja, ponieważ nie chcę tego pomylić i chciałbym przestrzegać jakiegoś standardu.
źródło
Odpowiedzi:
Właśnie zaimplementowałem moją usługę OWIN z elementem Bearer (nazywanym dalej access_token) i odświeżaniem tokenów. Mój wgląd w to jest taki, że możesz używać różnych przepływów. Zależy to więc od przepływu, którego chcesz użyć, jak ustawić czas wygaśnięcia access_token i refresh_token.
W dalszej części opiszę dwa przepływy A i B (proponuję, aby mieć przepływ B):
A) Czas wygaśnięcia access_token i refresh_token jest taki sam, jak domyślnie 1200 sekund lub 20 minut. Ten przepływ wymaga, aby Twój klient najpierw wysłał client_id i client_secret z danymi logowania, aby uzyskać access_token, refresh_token i expiration_time. Dzięki refresh_token można teraz uzyskać nowy access_token na 20 minut (lub cokolwiek ustawisz AccessTokenExpireTimeSpan w OAuthAuthorizationServerOptions na). Z tego powodu, że czas wygaśnięcia access_token i refresh_token jest taki sam, twój klient jest odpowiedzialny za uzyskanie nowego access_token przed upływem czasu! Np. Twój klient może wysłać wywołanie odświeżającego POST do punktu końcowego tokena z treścią (uwaga: w produkcji należy używać https)
otrzymać nowy token po np. 19 minutach, aby zapobiec wygaśnięciu tokenów.
B) w tym przepływie chcesz mieć krótkoterminowe wygaśnięcie access_token i długoterminowe wygaśnięcie dla refresh_token. Załóżmy, że dla celów testowych ustawiłeś access_token na wygaśnięcie za 10 sekund (
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)
), a refresh_token na 5 minut. Teraz chodzi o interesującą część ustawiania czasu wygaśnięcia refresh_token: Robisz to w swojej funkcji createAsync w klasie SimpleRefreshTokenProvider w następujący sposób:var guid = Guid.NewGuid().ToString(); //copy properties and set the desired lifetime of refresh token var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary) { IssuedUtc = context.Ticket.Properties.IssuedUtc, ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes //ExpiresUtc = DateTime.UtcNow.AddMonths(3) }; /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and *ExpiredUtc to the TICKET*/ var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties); //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary<string,AuthenticationTicket> // consider storing only the hash of the handle RefreshTokens.TryAdd(guid, refreshTokenTicket); context.SetToken(guid);
Teraz Twój klient może wysłać wywołanie POST z refresh_tokenem do punktu końcowego tokenu, gdy
access_token
wygasł. Część treści wywołania może wyglądać następująco:grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx
Ważną rzeczą jest to, że możesz chcieć użyć tego kodu nie tylko w funkcji CreateAsync, ale także w funkcji Create. Dlatego powinieneś rozważyć użycie własnej funkcji (np. O nazwie CreateTokenInternal) dla powyższego kodu. Tutaj możesz znaleźć implementacje różnych przepływów, w tym przepływ refresh_token (ale bez ustawiania czasu wygaśnięcia refresh_token)
Oto jedna przykładowa implementacja IAuthenticationTokenProvider na github (z ustawieniem czasu wygaśnięcia refresh_token)
Przykro mi, że nie mogę pomóc z innymi materiałami niż specyfikacje OAuth i dokumentacja Microsoft API. Zamieszczałbym tutaj linki, ale moja reputacja nie pozwala mi publikować więcej niż 2 linków ....
Mam nadzieję, że może to pomóc innym w zaoszczędzeniu czasu podczas próby zaimplementowania OAuth2.0 z czasem wygaśnięcia refresh_token innym niż czas wygaśnięcia access_token. Nie mogłem znaleźć przykładowej implementacji w sieci (poza tą z Thinktecture, do której link znajduje się powyżej) i zajęło mi to kilka godzin, zanim zadziałało.
Nowe informacje: W moim przypadku mam dwie różne możliwości otrzymania tokenów. Jednym z nich jest otrzymanie ważnego access_token. Tam muszę wysłać wywołanie POST z treścią String w formacie application / x-www-form-urlencoded z następującymi danymi
Po drugie, jeśli access_token nie jest już ważny, możemy wypróbować refresh_token, wysyłając wywołanie POST z treścią String w formacie
application/x-www-form-urlencoded
z następującymi danymigrant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID
źródło
RefreshTokens
, więc jeśliRefreshTokens
zostanie ujawniony, atakujący nie może użyć tych informacji !?Musisz zaimplementować RefreshTokenProvider . Najpierw utwórz klasę dla RefreshTokenProvider tj.
public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider { public override void Create(AuthenticationTokenCreateContext context) { // Expiration time in seconds int expire = 5*60; context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire)); context.SetToken(context.SerializeTicket()); } public override void Receive(AuthenticationTokenReceiveContext context) { context.DeserializeTicket(context.Token); } }
Następnie dodaj wystąpienie do OAuthOptions .
OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/authenticate"), Provider = new ApplicationOAuthProvider(), AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire), RefreshTokenProvider = new ApplicationRefreshTokenProvider() };
źródło
context.OwinContext.Environment
ZawieraMicrosoft.Owin.Form#collection
klucz, który dajeFormCollection
, gdzie można odnaleźć typ dotacji i dodać token odpowiednio. Wycieka implementacja, może się zepsuć w dowolnym momencie przy przyszłych aktualizacjach i nie jestem pewien, czy jest przenoszona między hostami OWIN.var form = await context.Request.ReadFormAsync();
var grantType = form.GetValue("grant_type");
następnie wystaw token odświeżania, jeśli typem grantu nie jest „refresh_token”Nie sądzę, że powinieneś używać tablicy do obsługi tokenów. Nie potrzebujesz też przewodnika jako tokena.
Możesz łatwo użyć context.SerializeTicket ().
Zobacz mój poniższy kod.
public class RefreshTokenProvider : IAuthenticationTokenProvider { public async Task CreateAsync(AuthenticationTokenCreateContext context) { Create(context); } public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { Receive(context); } public void Create(AuthenticationTokenCreateContext context) { object inputs; context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs); var grantType = ((FormCollection)inputs)?.GetValues("grant_type"); var grant = grantType.FirstOrDefault(); if (grant == null || grant.Equals("refresh_token")) return; context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays); context.SetToken(context.SerializeTicket()); } public void Receive(AuthenticationTokenReceiveContext context) { context.DeserializeTicket(context.Token); if (context.Ticket == null) { context.Response.StatusCode = 400; context.Response.ContentType = "application/json"; context.Response.ReasonPhrase = "invalid token"; return; } if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow) { context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; context.Response.ReasonPhrase = "unauthorized"; return; } context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays); context.SetTicket(context.Ticket); } }
źródło
Odpowiedź Freddy'ego bardzo mi pomogła. Aby uzyskać kompletność, oto jak zaimplementować haszowanie tokena:
private string ComputeHash(Guid input) { byte[] source = input.ToByteArray(); var encoder = new SHA256Managed(); byte[] encoded = encoder.ComputeHash(source); return Convert.ToBase64String(encoded); }
W
CreateAsync
:var guid = Guid.NewGuid(); ... _refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket); context.SetToken(guid.ToString());
ReceiveAsync
:public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { Guid token; if (Guid.TryParse(context.Token, out token)) { AuthenticationTicket ticket; if (_refreshTokens.TryRemove(ComputeHash(token), out ticket)) { context.SetTicket(ticket); } } }
źródło