2014-09-09 8 views
16

Ho cercato sul Web e non sono riuscito a trovare una soluzione al mio problema. Sto implementando OAuth nella mia app. Sto usando ASP .NET Web API 2 e Owin. Lo scenario è questo, una volta che una richiesta utente al punto finale Token, lui o lei riceverà un token di accesso con un token di aggiornamento per generare un nuovo token di accesso. Ho una classe che mi aiuta a generare un token di aggiornamento. Ecco:Come creare il token di aggiornamento con il provider di accesso esterno?

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider 
    { 


     private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>(); 



    public async Task CreateAsync(AuthenticationTokenCreateContext context) 
     { 

      var refreshTokenId = Guid.NewGuid().ToString("n"); 
      using (AuthRepository _repo = new AuthRepository()) 
      { 
       var refreshTokenLifeTime = context.OwinContext.Get<string>         ("as:clientRefreshTokenLifeTime"); 
       var token = new RefreshToken() 
       { 
        Id = Helper.GetHash(refreshTokenId), 
        ClientId = clientid, 
        Subject = context.Ticket.Identity.Name, 
        IssuedUtc = DateTime.UtcNow, 
        ExpiresUtc = DateTime.UtcNow.AddMinutes(15) 
       }; 
       context.Ticket.Properties.IssuedUtc = token.IssuedUtc; 
       context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc; 
       token.ProtectedTicket = context.SerializeTicket(); 
       var result = await _repo.AddRefreshToken(token); 
       if (result) 
       {   
        context.SetToken(refreshTokenId); 
       } 
      } 
     } 

     // this method will be used to generate Access Token using the Refresh Token 
     public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) 
     { 

      string hashedTokenId = Helper.GetHash(context.Token); 
      using (AuthRepository _repo = new AuthRepository()) 
      { 
       var refreshToken = await _repo.FindRefreshToken(hashedTokenId); 
       if (refreshToken != null) 
       { 
        //Get protectedTicket from refreshToken class 
        context.DeserializeTicket(refreshToken.ProtectedTicket); 
        // one refresh token per user and client 
        var result = await _repo.RemoveRefreshToken(hashedTokenId); 
       } 
      } 
     } 

     public void Create(AuthenticationTokenCreateContext context) 
     { 
      throw new NotImplementedException(); 
     } 

     public void Receive(AuthenticationTokenReceiveContext context) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

ora sto permettendo ai miei utenti di registrarsi attraverso facebook. Una volta che un utente si registra con facebook, creo un token di accesso e glielo do. Devo generare anche un token di aggiornamento? All'inizio mi viene in mente, è di generare un token di accesso lungo come un giorno, quindi questo utente deve riconnettersi con facebook. Ma se non voglio farlo, posso dare al client, un token di aggiornamento, e lui può usarlo per aggiornare il token di accesso generato e ottenere un nuovo. Come posso creare il token di aggiornamento e collegarlo alla risposta quando qualcuno si registra o effettua il login con facebook o esternamente?

Ecco il mio esterno API di registrazione

public class AccountController : ApiController 
    { 
     [AllowAnonymous] 
     [Route("RegisterExternal")] 
     public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model) 
     { 

     if (!ModelState.IsValid) 
     { 
      return BadRequest(ModelState); 
     } 
     var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName); 
     return Ok(accessTokenResponse); 
     } 


    } 

// Metodo privato per generare token di accesso

private JObject GenerateLocalAccessTokenResponse(string userName) 
     { 

      var tokenExpiration = TimeSpan.FromDays(1); 
      ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType); 
      identity.AddClaim(new Claim(ClaimTypes.Name, userName)); 
      identity.AddClaim(new Claim("role", "user")); 
      var props = new AuthenticationProperties() 
      { 
       IssuedUtc = DateTime.UtcNow, 
       ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration), 
      }; 
      var ticket = new AuthenticationTicket(identity, props); 
      var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket); 
      JObject tokenResponse = new JObject(
             new JProperty("userName", userName), 
             new JProperty("access_token", accessToken), 
             // Here is what I need 
             new JProperty("resfresh_token", GetRefreshToken()), 
             new JProperty("token_type", "bearer"), 
             new JProperty("refresh_token",refreshToken), 
             new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()), 
             new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()), 
             new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString()) 
     ); 
      return tokenResponse; 
     } 
+1

Eventuali aggiornamenti su tale argomento? Ho lo stesso identico problema. – iPeo

risposta

20

ho speso un sacco di tempo per trovare la risposta a questa domanda. Quindi, sono felice di aiutarti.

1) Modificare il metodo ExternalLogin. Di solito si presenta come:

if (hasRegistered) 
{ 
    Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie); 

    ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager, 
       OAuthDefaults.AuthenticationType); 
    ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager, 
       CookieAuthenticationDefaults.AuthenticationType); 

    AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName); 
    Authentication.SignIn(properties, oAuthIdentity, cookieIdentity); 
} 

Ora, in realtà, è necessario aggiungere refresh_token. metodo sarà simile a questa:

if (hasRegistered) 
{ 
    Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie); 

    ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager, 
        OAuthDefaults.AuthenticationType); 
    ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager, 
        CookieAuthenticationDefaults.AuthenticationType); 

    AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName); 

    // ADD THIS PART 
    var ticket = new AuthenticationTicket(oAuthIdentity, properties); 
    var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket); 

       Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
        new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
         Request.GetOwinContext(), 
         Startup.OAuthOptions.AccessTokenFormat, ticket); 

    await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context); 
    properties.Dictionary.Add("refresh_token", context.Token); 

    Authentication.SignIn(properties, oAuthIdentity, cookieIdentity); 
} 

Ora i refrehs token viene generato.

2) C'è un problema nell'utilizzo del contesto di base.SerializeTicket in SimpleRefreshTokenProvider Metodo CreateAsync. Messaggio Bit Of Technology

Sembra nel metodo ReceiveAsync, il context.DeserializeTicket non è restituire un ticket di autenticazione a tutti nel caso di accesso esterno. Quando guardo il contesto. Proprietà del lotto dopo quella chiamata è nullo. Confrontandolo con il flusso di accesso locale, il metodo DeserializeTicket imposta la proprietà context.Ticket su AuthenticationTicket. Quindi il mistero è ora come il DeserializeTicket si comporta in modo diverso in i due flussi. La stringa di biglietti protetta nel database viene creato nello stesso metodo CreateAsync, che differiscono solo nel senso che io chiamo metodo manualmente nella GenerateLocalAccessTokenResponse, contro l'Owin middlware chiamando normalmente ... E nemmeno SerializeTicket o DeserializeTicket genera un errore ...

Quindi, è necessario utilizzare Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer per searizzare e deserializzare il ticket. Sarà simile a questa:

Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer 
       = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer(); 

token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket)); 

invece di:

token.ProtectedTicket = context.SerializeTicket(); 

E per il metodo ReceiveAsync:

Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer(); 
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket))); 

invece di:

context.DeserializeTicket(refreshToken.ProtectedTicket); 

3) Ora devi aggiungi refresh_token alla risposta del metodo ExternalLogin. Ignora AuthorizationEndpointResponse in OAuthAuthorizationServerProvider. Qualcosa del genere:

public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context) 
{ 
    var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"]; 
    if (!string.IsNullOrEmpty(refreshToken)) 
    { 
      context.AdditionalResponseParameters.Add("refresh_token", refreshToken); 
    } 
    return base.AuthorizationEndpointResponse(context); 
} 

Quindi .. questo è tutto! Ora, dopo aver chiamato ExternalLogin metodo, si ottiene url: https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL

Spero che questo aiuta)

+0

grazie, per il tuo impegno. In realtà stavo usando questo codice per un progetto che ho smesso di lavorarci.Tuttavia, proverò questo codice, una volta che riprendo a lavorarci e ti farò sapere il mio feedback. Ma ancora una volta, grazie mille per il tuo impegno apprezzo molto il tuo aiuto. – user123456

+1

Felice di aiutare) Sentitevi liberi di fare domande. – Giraffe

+0

Grazie mille, tantissimo !!! –

5

@giraffe e altri offcourse

Qualche osservazione. Non è necessario utilizzare il contatore personalizzato tickerserializer.

La seguente riga:

Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
       new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
        Request.GetOwinContext(), 
        Startup.OAuthOptions.AccessTokenFormat, ticket); 

Come tokenformat: Startup.OAuthOptions.AccessTokenFormat viene utilizzato. Dal momento che vogliamo fornire un refeshtoken questo ha bisogno di te per essere cambiato: Startup.OAuthOptions.RefreshTokenFormat

In caso contrario, se si vuole ottenere un nuovo access token e aggiornare la refreshtoken (grant_type = refresh_token & refresh_token = ......) il deserializzatore/unprotector avrà esito negativo. Dal momento che utilizza le parole chiave a scopi errati nella fase di decrittografia.

+0

Grazie per il commento! Forse hai ragione, ma nel mio caso, il processo funziona correttamente. La prossima volta proverò ad implementare la tua variante! – Giraffe

+0

Tnx man! Questo ha funzionato per me. – qorsmond

4

Finalmente trovato la soluzione per il mio problema. Prima di tutto, se incontri MAI qualche problema con OWIN e non riesci a capire cosa non va, ti consiglio di abilitare semplicemente il debugging dei simboli e il debugging. Una grande spiegazione può essere trovata qui: http://www.symbolsource.org/Public/Home/VisualStudio

Il mio errore era semplicemente, che stavo calcolando un ExiresUtec errato quando si utilizzano provider di accesso esterni. Quindi il mio refreshtoken fondamentalmente era sempre scaduto subito ....

Se si implementa aggiornamento gettoni, poi guardare a questo articolo del blog gread: http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

E per farlo funzionare con i token di aggiornamento per i fornitori esterni, è necessario impostare i due parametri requried ("come: clientAllowedOrigin" e "come: clientRefreshTokenLifeTime") del contesto così invece di

 

var ticket = new AuthenticationTicket(oAuthIdentity, properties); 
var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
        Request.GetOwinContext(), 
        Startup.OAuthOptions.AccessTokenFormat, ticket); 

await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context); 
properties.Dictionary.Add("refresh_token", context.Token); 

è necessario per ottenere il cliente prima e impostare il contesto parametri

01.235.
 

    // retrieve client from database 
    var client = authRepository.FindClient(client_id); 
    // only generate refresh token if client is registered 
    if (client != null) 
    { 
     var ticket = new AuthenticationTicket(oAuthIdentity, properties); 
     var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket); 
     // Set this two context parameters or it won't work!! 
     context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin); 
     context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString()); 

     await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context); 
     properties.Dictionary.Add("refresh_token", context.Token); 
    } 

Problemi correlati