2016-03-20 4 views
2

Ich habe einen Autorisierungsserver nach dem ArtikelWo überprüft OAuthAuthorizationServerProvider das Token-Ablaufdatum aktualisieren?

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

ich es fast genau wie der Artikel implementiert habe umgesetzt, aber ich sehe nicht, wie der Auth-Server weiß, werden die Aktualisierungs-Token abgelaufen. Tatsächlich habe ich getestet und der Server gewährt keine Zugriffstoken, wenn abgelaufene Aktualisierungstoken angezeigt werden, aber ich sehe die Logik dafür in meinem Authentifizierungsserver nicht. Wenn ich ein Zugriffstoken unter Verwendung eines abgelaufenen Aktualisierungstokens anfordere, wird die Unterklasse my OAuthAuthorizationServerProvider nie aufgerufen. Tatsächlich wird keine meiner Methoden in meiner abgeleiteten Klasse OAuthAuthorizationServerProvider oder meiner IAuthenticationTokenProvider-Implementierung aufgerufen, wenn ich ein neues Zugriffstoken mit einem abgelaufenen Token anfordere Aktualisierungstoken Jede Hilfe wird geschätzt. Hier ist, was ich habe

public class SmartCardOAuthAuthenticationTokenProvider : IAuthenticationTokenProvider 
{ 
    private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess; 
    public SmartCardOAuthAuthenticationTokenProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess) 
    { 
     _producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess; 

    } 
    public async Task CreateAsync(AuthenticationTokenCreateContext context) 
    { 
     var clientid = context.Ticket.Properties.Dictionary["as:client_id"]; 

     if (string.IsNullOrEmpty(clientid)) 
     { 
      return; 
     } 

     var refreshTokenId = Guid.NewGuid().ToString("n"); 

     using(IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory())//using (IAuthorizationDataAccess _repo = new AuthRepository()) 
     { 
      IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>(); 

      var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); 

      var token = new RefreshToken() 
      { 
       RefreshTokenId = Helper.GetHash(refreshTokenId), 
       ClientId = clientid, 
       Subject = context.Ticket.Identity.Name, 
       IssuedUtc = DateTime.UtcNow, 
       ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) 
      }; 

      context.Ticket.Properties.IssuedUtc = token.IssuedUtc; 
      context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc; 

      token.ProtectedTicket = context.SerializeTicket(); 

      var result = await _repo.AddRefreshTokenAsync(token); 

      if (result) 
      { 
       context.SetToken(refreshTokenId); 
      } 

     } 
    } 

    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) 
    { 

     var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); 

     string hashedTokenId = Helper.GetHash(context.Token); 

     //using (IAuthorizationDataAccess _repo = new AuthRepository()) 
     //{ 
     using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository()) 
     { 
      IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>(); 

      var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId); 

      if (refreshToken != null) 
      { 
       //Get protectedTicket from refreshToken class 
       context.DeserializeTicket(refreshToken.ProtectedTicket); 
       var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId); 
      } 
     } 
    } 

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

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

public class SmartCardOAuthAuthorizationProvider : OAuthAuthorizationServerProvider 
{ 
    private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess; 
    public SmartCardOAuthAuthorizationProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess) 
    { 
     _producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess; 

    } 
    public override System.Threading.Tasks.Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); 

     if (allowedOrigin == null) allowedOrigin = "*"; 

     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); 

     if (context.UserName != "onlyOneHardCodedUserForSakeOfExploration" && context.Password!="thePassword") 
     { 
      context.SetError("invalid_grant", "the user name or password is incorrect"); 
      return Task.FromResult<object>(null); ; 
     } 
     ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType); 
     identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); 
     identity.AddClaim(new Claim("sub", context.UserName)); 
     identity.AddClaim(new Claim(ClaimTypes.Role, "PostVSDebugBreakModeEnterEventArgs")); 
     identity.AddClaim(new Claim(DatawareClaimTypes.SmartCardUserId.ToString(), 1.ToString())); 

     var props = new AuthenticationProperties(new Dictionary<string, string> 
      { 
       { 
        "as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId 
       }, 
       { 
        "userName", context.UserName 
       } 
      }); 

     var ticket = new AuthenticationTicket(identity, props); 
     //ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddDays(2)); 
     context.Validated(ticket); 

     return Task.FromResult<object>(null); 
    } 

    public override System.Threading.Tasks.Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) 
    { 

     string clientId = string.Empty; 
     string clientSecret = string.Empty; 
     Client client = null; 

     if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) 
     { 
      context.TryGetFormCredentials(out clientId, out clientSecret); 
     } 

     if (context.ClientId == null) 
     { 
      //Remove the comments from the below line context.SetError, and invalidate context 
      //if you want to force sending clientId/secrects once obtain access tokens. 
      //context.Validated(); 
      context.SetError("invalid_clientId", "ClientId should be sent."); 
      return Task.FromResult<object>(null); 
     } 

     string[] clientIdClientUnique = context.ClientId.Split(':'); 

     if (clientIdClientUnique == null || clientIdClientUnique.Length <= 1) 
     { 
      context.SetError("invalid_client_unique"); 
      return Task.FromResult<object>(null); 
     } 

     clientId = clientIdClientUnique[0]; 
     string clientUnique = clientIdClientUnique[1]; 

     using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository()) 
     { 
      IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>(); 


      client = _repo.FindClient(clientId);//new Client { Active = true, AllowedOrigin = "*", ApplicationType = ApplicationTypes.DesktopClient, ClientId = context.ClientId, Name = "Visual Studio Event Source", RefreshTokenLifeTimeInMinutes = 14400, Secret = Helper.GetHash(clientSecret) };//_repo.FindClient(context.ClientId); 
     } 

     if (client == null) 
     { 
      //context.SetError("invalid_client_unique"); 
      context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId)); 
      return Task.FromResult<object>(null); 
     } 

     if (string.IsNullOrWhiteSpace(clientSecret)) 
     { 
      context.SetError("invalid_clientId", "Client secret should be sent."); 
      return Task.FromResult<object>(null); 
     } 
     else 
     { 
      if (client.Secret != Helper.GetHash(clientSecret)) 
      { 
       context.SetError("invalid_clientId", "Client secret is invalid."); 
       return Task.FromResult<object>(null); 
      } 
     } 

     if (!client.Active) 
     { 
      context.SetError("invalid_clientId", "Client is inactive."); 
      return Task.FromResult<object>(null); 
     } 

     context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin); 
     context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTimeInMinutes.ToString()); 

     context.Validated(); 
     return Task.FromResult<object>(null); 
    } 

    public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) 
    { 
     var originalClient = context.Ticket.Properties.Dictionary["as:client_id"]; 
     var currentClient = context.ClientId; 

     if (originalClient != currentClient) 
     { 
      context.SetError("invalid_clientId", "Refresh token is issued to a different clientId."); 
      return Task.FromResult<object>(null); 
     } 

     // Change auth ticket for refresh token requests 
     var newIdentity = new ClaimsIdentity(context.Ticket.Identity); 
     newIdentity.AddClaim(new Claim("newClaim", "newValue")); 

     var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties); 
     context.Validated(newTicket); 

     return Task.FromResult<object>(null); 
    } 

    public override Task TokenEndpoint(OAuthTokenEndpointContext context) 
    { 
     foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) 
     { 
      context.AdditionalResponseParameters.Add(property.Key, property.Value); 
     } 

     return Task.FromResult<object>(null); 
    } 

} 

Auch wenn ich eine Anfrage

request with expired refresh und die Aktualisierungs-Token abgelaufen sind, keines der oben genannten Klassen \ Methoden getroffen zu werden. Darüber hinaus ist das Aktualisierungstoken, das an die API übergeben wird, nichts anderes als die GUID, die ich in der SmartCardOAuthAuthenticationTokenProvider.CreateAsync generiert hat, die keine Informationen über Ablauf enthält. Wenn keine der oben genannten Methoden bei der Anforderung von Zugriff durch Aktualisierung angetroffen wird und die Anforderung nichts weitergibt (es sieht nach nichts aus), wenn ein neues Zugriffstoken durch Aktualisierung angefordert wird, wie weiß der Server dann, dass das Aktualisierungstoken abgelaufen ist?

Sieht aus wie Magie für mich.

UPDATE 1 - Hinzufügen Startup Code

public static class OwinStartUpConfig 
{ 
    public static void Configure(HttpConfiguration configFromOwinStartup) 
    { 
     configFromOwinStartup.MapHttpAttributeRoutes(); 
     configFromOwinStartup.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional }); 

     var jsonFormatter = configFromOwinStartup.Formatters.OfType<JsonMediaTypeFormatter>().First(); 
     jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

     RegiserDependencies(configFromOwinStartup); 

    } 

    public static void RegiserDependencies(HttpConfiguration configFromOwinStartup) 
    { 
     string connectionStringForSmartCardDbCntx = System.Configuration.ConfigurationManager.ConnectionStrings["SmartCardDataContext"].ConnectionString; 
     string projectNameWhenNewProjectCreatedDueToNoMatch = System.Configuration.ConfigurationManager.AppSettings["ProjectNameWhenNewProjectCreatedDueToNoMatch"]; 

     Autofac.ContainerBuilder builderUsedToRegisterDependencies = new Autofac.ContainerBuilder(); 

     builderUsedToRegisterDependencies.RegisterType<DataAccessFactoryFactoryEf>() 
      .As<IDataAccessFactoryFactory>() 
      .WithParameter(new TypedParameter(typeof(string), connectionStringForSmartCardDbCntx)); 

     builderUsedToRegisterDependencies.Register(
      c => 
        new List<IProjectActivityMatch<VSDebugBreakModeEnterActivity>> 
      { 
       new MatchVSProjectWithMostRecentActivity<VSDebugBreakModeEnterActivity>(), 
       new MatchVSSolutionWithMostRecentActivityActivityMatch<VSDebugBreakModeEnterActivity>(), 
       new MatchMostRecentActivityMatch<VSDebugBreakModeEnterActivity>(), 
       new MatchToNewProjectActivityMatch<VSDebugBreakModeEnterActivity>(projectNameWhenNewProjectCreatedDueToNoMatch) 
      } 
     ).As<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>(); 

     builderUsedToRegisterDependencies 
      .RegisterType<MatchDontGiveUpActivityMatch<VSDebugBreakModeEnterActivity>>() 
      //.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>("VSDebugBreakModeEnterActivityMatchers")) 
      .As<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>(); 

     builderUsedToRegisterDependencies 
      .RegisterType<VSDebugBreakModeEnterEventArgsEventSaver>() 
      .Named<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver"); 

     builderUsedToRegisterDependencies 
      .RegisterType<VSDebugBreakModeEnterEventArgsController>() 
      .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver")); 

     var container = builderUsedToRegisterDependencies.Build(); 

     configFromOwinStartup.DependencyResolver = new AutofacWebApiDependencyResolver(container); 

    } 
} 
    public static class OAuthStartupConfig 
{ 
    internal static void Configure(IAppBuilder app) 
    { 
     OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions 
     { 
      AllowInsecureHttp = false, 
      TokenEndpointPath = new PathString("/token"), 
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), //TimeSpan.FromDays(1), 
      Provider = new SmartCardOAuthAuthorizationProvider(new AuthorizationDataAccessFactoryFactory()), 
      RefreshTokenProvider = new SmartCardOAuthAuthenticationTokenProvider(new AuthorizationDataAccessFactoryFactory()) 
     }; 
     app.UseOAuthAuthorizationServer(oAuthServerOptions); 
     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 
    } 
} 

UPDATE - In Reaktion auf die Kommentare

request for access token by pass returns access and refresh token

Das Zugriffstoken in der obigen Antwort Ansprüche Informationen enthält und den Ablauf Informationen für den Zugriffstoken . All diese Informationen werden vom Server in das oben genannte Zugriffstoken serialisiert. So kann ich sehen, wie der Ablauf des Zugriffstokens überprüft wird. Was ist jedoch mit dem Aktualisierungstoken? Wenn eine Anforderung für einen Zugriffstoken wird mit den Aktualisierungs-Token machen:

enter image description here

In der Anfrage über den Aktualisierungs-Token gut ist und so die Anforderung für die Zugriffstoken durch Aktualisierungs-Token gewährt wird, aber wenn der Auffrischungs Token abgelaufen sind, wie würde OAuthAuthorizationServerProvider das Refresh-Token auf Ablauf überprüfen?

Auch hier habe ich überprüft, ob der oben bereitgestellte Code tatsächlich nach abgelaufenen Refresh-Tokens sucht und kein Zugriffstoken gewährt, wenn das Refresh-Token abgelaufen ist, aber WIE weiß es? Ich habe nichts geschrieben, um den Ablauf des Aktualisierungs-Tokens in meiner abgeleiteten Klasse OAuthAuthorizationServerProvider zu überprüfen. Also dann wie ????

UPDATE

Die Magie geschieht in der ReceiveAsync Methode der IAuthenticationTokenProvider Umsetzung.

public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) 
    { 

     var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); 

     string hashedTokenId = Helper.GetHash(context.Token); 

     //using (IAuthorizationDataAccess _repo = new AuthRepository()) 
     //{ 
     using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository()) 
     { 
      IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>(); 

      var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId); 

      if (refreshToken != null) 
      { 
       //Get protectedTicket from refreshToken class 
       context.DeserializeTicket(refreshToken.ProtectedTicket); 
       var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId); 
      } 
     } 
    } 

Besonders die Linie context.DeserializeTicket(refreshToken.ProtectedTicket); ist die Magie. Dadurch wird die context.Ticket-Eigenschaft festgelegt. Nachdem die Methode ReceiveAsync abgeschlossen ist. Keine Notwendigkeit, manuell etwas zu überprüfen OWIN, irgendwo hinter den Kulissen, weiß, dass das Ticket abgelaufen ist.

+0

Können Sie den Code anzeigen, in dem Sie Ihren Provider mit den OAuthServerOptions verbinden? Ich vermute, dass Sie beim Erstellen der Serveroptionen nicht auf einen Aktualisierungstokenanbieter zeigen. –

Antwort

1

Ich habe es fast genau wie im Artikel implementiert, aber ich sehe nicht, wie der Authentifizierungsserver das Refresh-Token abgelaufen ist.

Sie müssen diese Informationen irgendwo speichern, zum Beispiel in einer SQL-Tabelle. Wenn Sie eine Anforderung zum Ausgeben eines neuen Tokens mit einem Aktualisierungstoken erhalten, müssen Sie überprüfen, ob das übermittelte Aktualisierungstoken noch gültig ist, basierend auf den Informationen, die Sie zu Ihrer lokalen Quelle der Wahrheit haben.

Zum Beispiel ist dies die ich es umgesetzt:

public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) 
    { 
     var allowedOrigin = context.OwinContext.Get<string>(Constants.PublicAuth.CLIENT_ALLOWED_ORIGIN); 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {allowedOrigin}); 

     RefreshTokenModel refreshToken = await _mediator.SendAsync(new VerifyRefreshToken(context.Token)); 

     if (refreshToken != null) 
     { 
      context.DeserializeTicket(refreshToken.ProtectedTicket); 
     } 
    } 

Und dies ist die Abfrage-Handler:

public async Task<RefreshTokenModel> Handle(VerifyRefreshToken query) 
     { 
      RefreshToken refreshToken = await Context.RefreshTokens 
       .Where(rt => rt.Token == query.Token) 
       .FirstOrDefaultAsync(); 

      if (refreshToken == null || refreshToken.ExpiresUtc < DateTimeOffset.UtcNow) return null; 

      User user = await Context.Users.Where(u => u.Email == refreshToken.Subject).SingleOrDefaultAsync(); 
      if (user == null || (user.LockoutEnabled && user.LockoutEndUtc > DateTimeOffset.UtcNow)) return null; 

      return new RefreshTokenModel 
      { 
       ProtectedTicket = new UTF8Encoding(true).GetString(refreshToken.ProtectedTicket) 
      }; 
     } 

Wie Sie sehen, ob das nicht existiert, oder das Token Verfallspunkt ist abgelaufen, ich gebe null zurück; Null bedeutet kein Token, also gibt ReceiveAsync(AuthenticationTokenReceiveContext context) nichts zurück und es wird 401 Unauthorized ausgegeben.

+0

Vielen Dank für Ihre Antwort. Allerdings habe ich die Logik, die Sie in meiner ReceiveAsync-Methode vorschlagen, nicht umgesetzt UND der Token-Ablauf funktioniert immer noch. Der Code, den ich gepostet habe, erkennt verfallene Tokens, aber ich kann nicht herausfinden, wie, weil ich keinen solchen Code implementiert habe. Das verstehe ich nicht. – andrewramka

+0

Wenn Sie "Token" sagen, meinen Sie den Träger? Da das Token das Ablaufdatum enthält, das darin erstellt wird (was der Code AccessTokenExpireTimeSpan = TimeSpan.FromDays (1)) bedeutet. Der andere ist nur ein "persönlicher" Code (eine GUID hier), also hat er keinen Ablauf irgendeiner Art, es ist wie ein Schlüssel. –

+0

Das Zugriffstoken hat einen Ablauf, aber was ist mit dem Aktualisierungstoken? Ich habe einen neuen Screenshot hinzugefügt und beide Tokens vom Authentifizierungsserver zurückgesendet. Es gibt ein Zugriffstoken (ja, diese Zeichenfolge enthält Ansprüche, Ablauf und andere Informationen) und es gibt auch ein Aktualisierungstoken (diese Zeichenfolge ist eine GUID, die auf einen Aktualisierungstokeneintrag in der Datenbank verweist). Der Server kann ein Zugriffstoken einfach überprüfen, da das Token die korrekten Informationen enthält. Wie validiert der Server jedoch das Aktualisierungstoken, wenn Anforderungen für ein neues Zugriffstoken mit dem Aktualisierungstoken eingehen? Das ist die Frage. – andrewramka

0

Die Magie passiert in der ReceiveAsync-Methode der IAuthenticationTokenProvider-Implementierung.

Insbesondere die Zeile context.DeserializeTicket (refreshToken.ProtectedTicket); ist die Magie, die ich vermisste. Dadurch wird die context.Ticket-Eigenschaft festgelegt. Nachdem die Methode ReceiveAsync abgeschlossen ist. Keine Notwendigkeit, manuell etwas zu überprüfen OWIN, irgendwo hinter den Kulissen, weiß, dass das Ticket abgelaufen ist.

Verwandte Themen