17

Neu bei Dependency Injection, so ist dies wahrscheinlich eine einfache Sache, aber ich habe versucht und kann es nicht herausfinden, ich verwende Simple Injector.Dependency Injection (mit SimpleInjector) und OAuthAuthorizationServerProvider

Ich habe ein WebApi, das SimpleInjector vollkommen in Ordnung benutzt, jetzt möchte ich Sicherheit mit OAuth implementieren.

Um dies zu tun, begann ich dieses Tutorial zu folgen, was sehr hilfreich ist, tut aber Dependency Injection

http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

Ich habe meine global.asax-Datei wie folgt aussehen, die Einrichtung Dependency Injection verwenden (Arbeits perfekt)

protected void Application_Start() 
{ 
    SimpleInjectorConfig.Register(); 

    GlobalConfiguration.Configure(WebApiConfig.Register); 
} 

ich habe eine Startup.Auth.cs Datei erstellt OAuth

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     var OAuthServerOptions = new OAuthAuthorizationServerOptions() 
     { 
      AllowInsecureHttp = true, 
      TokenEndpointPath = new PathString("/token"), 
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
      Provider = new MyAuthorizationServerProvider() // here is the problem 
     }; 

     // Token Generation 
     app.UseOAuthAuthorizationServer(OAuthServerOptions); 
     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 
    } 
} 
konfigurieren

Nun, wie ich oben kommentierte, MyAuthorizationServerProvider ist das Problem. Es benötigt einen Parameter von IUserService, den ich normalerweise einfüge. Ich möchte den Konstruktor nicht leeren, weil mein IUserService auch ein Repository einfügt. Hier ist die Datei

public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider 
{ 
    private IUserService _service; 
    public ApiAuthorizationServerProvider (IUserService service) 
    { 
     _service = service; 
    } 

    public override async Task ValidateClientAuthentication(
     OAuthValidateClientAuthenticationContext context) 
    { 
     context.Validated(); 
    } 

    public override async Task GrantResourceOwnerCredentials(
     OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", 
      new[] { "*" }); 

     IUserService service = Startup.Container.GetInstance<IUserService>(); 
     User user = _service.Query(e => e.Email.Equals(context.UserName) && 
      e.Password.Equals(context.Password)).FirstOrDefault(); 

     if (user == null) 
     { 
      context.SetError("invalid_grant", 
       "The user name or password is incorrect."); 
      return; 
     } 

     var identity = new ClaimsIdentity(context.Options.AuthenticationType); 
     identity.AddClaim(new Claim("sub", context.UserName)); 
     identity.AddClaim(new Claim("role", "user")); 

     context.Validated(identity); 

    } 
} 

Wie kann ich diese Arbeit mit Injection Abhängigkeit? Das muss ziemlich viel passieren und muss etwas damit umgehen können. Ich bin sicher, es ist etwas Einfaches, aber ich lerne immer noch.

+1

Hoffe das hilft https: //simpleininjektor.codeplex. com/diskussionen/564822 – DSR

+0

hast du eine andere lösung gefunden? – moyomeh

+0

Ich verwende OpenIddict von Github und nur den Rollennamen als Anspruch speichern. Berechtigungen werden nicht gespeichert, da die Änderung auf dem Server sofort erfolgt, wenn etwas aktualisiert wird. Ich muss die Berechtigungen nur einmal pro Anfrage überprüfen, also ist das in Ordnung für mich atm – Gillardo

Antwort

8

Wenn Sie mit Dependency Injection beginnen, ist Owin wahrscheinlich nicht die freundlichste API für den Anfang.

bemerkte ich diesen Teil im Code:

IUserService service = Startup.Container.GetInstance<IUserService>(); 

Sie tun dies als Behelfslösung wahrscheinlich, bevor Sie herausfinden, wie der Konstruktor zu verwenden. Aber ich denke, das ist deine Antwort genau dort. Der OAuthAuthorizationServerProvider ist ein Singleton, so dass Ihr IUserService auch ein Singleton ist und alle Abhängigkeiten dieser Klasse ebenfalls Singleton sind.

Sie haben erwähnt, dass Sie in Ihrem Benutzerservice ein Repository verwenden. Wahrscheinlich möchten Sie nicht, dass dieses Repository Singleton ist, da ich annimmt, dass dieses Repository einen DbContext irgendeiner Art verwendet.

So könnte die Zwischenantwort die Lösung sein, die Sie bereits gemacht haben. Vielleicht gibt es eine elegantere Lösung, wenn Sie herausfinden, was die UseOAuthAuthorizationServer-Methode genau macht. Der Quellcode von Katana kann hier gefunden werden: Katana source code

Für die Registrierung der anderen asp.net-Identitätsklassen wird der Link im Kommentar von DSR Ihnen einen guten Ausgangspunkt geben.

+0

Dann denke ich, ich habe eine vorherige Antwort missverstanden. Ich habe diesen http://StackOverflow.com/Questions/25997592/dependency-injection-using-simple-injector-and-oauthauthorization-Serverprovider added und ich vermute, der obige Code war nicht die Antwort? Wie ich sagte, bin ich neu, also nicht sicher, dass ich deine Antwort verstehe? Kannst du bitte etwas mehr erklären? – Gillardo

+0

Sind Sie sicher, dass der Link korrekt ist? Es verlinkt auf diesen Beitrag! Der Code, den ich von Ihrer Frage kopiert habe, ist eine gute Zwischenlösung, da Sie den Container jedes Mal nach einer neuen Instanz fragen, wenn die Methode 'GrantResourceOwnerCredentials' aufgerufen wird. Dies ist eine gute Sache, da sonst während der gesamten Lebensdauer der Anwendung nur eine einzige Instanz Ihres Benutzerservices vorhanden ist. –

+0

Sorry, ich habe zwischen 2 Posts, die ich auf Stackoverflow habe, verwirrt. Ich dachte, du hast auf einen anderen Beitrag hier geantwortet http://stackoverflow.com/questions/26002866/unable-to-register-api-controller-using-simple-injector Ich habe tatsächlich diesen Code auf einen anderen Beitrag ändern – Gillardo

23

Ich brauchte einige Zeit, um herauszufinden, ob es möglich wäre, OAuthAuthorizationServerOptions im Owin pipeling mit der app.Use() Methode direkt, statt app.UseOAuthAuthorizationServer() zu registrieren, die nur eine Erweiterung Methode über app.Use() ist. app.Use() hat eine Überladung, wo Sie einen Delegaten registrieren können, den Sie verwenden könnten, um die OAuthAuthorizationServerOptions zu erstellen.

Leider ist diese Bemühung in eine Sackgasse geraten, denn selbst wenn wir einen Delegaten für die Konstruktion verwenden würden, wird dieser höchstwahrscheinlich nur einmal von der Owin-Pipeline aufgerufen, was zum gleichen Ergebnis führt, nämlich zu einem Singleton Instanz der OAuthAuthorizationServerOptions und damit alle Abhängigkeiten dieser Klasse wird auch Singleton sein.

Also die einzige Lösung, um die Dinge so zu halten, wie sie sein sollten, ist eine neue Instanz Ihres UserService jedes Mal zu ziehen, wenn die GrantResourceOwnerCredentials() Methode aufgerufen wird.

Aber um die Simple Injector design principles zu folgen, wäre es schlechtes Design, eine Abhängigkeit von dem Container in der ApiAuthorizationServerProvider Klasse zu halten, wie der ursprüngliche Code zeigt.

Eine bessere Möglichkeit wäre, eine Fabrik für die Klasse UserService zu verwenden, anstatt sie direkt aus dem Container zu ziehen. Der nächste Code zeigt ein Beispiel, wie Sie das tun könnten:

Zunächst reinigen Sie die Application_Start() Methode in Ihrer Datei global.asax und legen Sie Ihren gesamten Startup-Code in der Owin Startup()-Methode. Der Code des Startup() Methode:

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     var container = SimpleInjectorConfig.Register(); 

     GlobalConfiguration.Configure(WebApiConfig.Register); 

     Func<IUserService> userServiceFactory =() => 
       container.GetInstance<IUserService>(); 

     var OAuthServerOptions = new OAuthAuthorizationServerOptions() 
     { 
      AllowInsecureHttp = true, 
      TokenEndpointPath = new PathString("/token"), 
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
      Provider = new ApiAuthorizationServerProvider(userServiceFactory) 
     }; 

     // Token Generation 
     app.UseOAuthAuthorizationServer(OAuthServerOptions); 
     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); 
    } 
} 

Beachten Sie, wie ich die Signatur der SimpleInjectorConfig.Register() Funktion geändert durch die komplett konfiguriert Einfache Injector Container an den Aufrufer zurückkehrt, so kann es direkt verwendet werden.

nun den Konstruktor Ihrer ApiAuthorizationServerProvider Klasse ändern, so kann die Factory-Methode injiziert werden:

public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider 
{ 
    private Func<IUserService> userServiceFactory; 

    public ApiAuthorizationServerProvider(Func<IUserService> userServiceFactory) 
    { 
     this.userServiceFactory = userServiceFactory; 
    } 

    // other code deleted for brevity... 

    private IUserService userService 
    { 
     get 
     { 
      return this.userServiceFactory.Invoke(); 
     } 
    } 

    public override async Task GrantResourceOwnerCredentials(
     OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     // other code deleted for brevity... 
     // Just use the service like this 
     User user = this.userService.Query(e => e.Email.Equals(context.UserName) && 
      e.Password.Equals(context.Password)).FirstOrDefault(); 

     // other code deleted for brevity... 
    } 
} 

So können Sie ein neues UserService jedes Mal bekommen die GrantResourceOwnerCredentials() Methode aufgerufen wird und die vollständigen Abhängigkeitsgraphen hinter der UserService Klasse wird den Lebensdauern folgen, die Sie in Ihrer Simple Injector-Konfiguration definiert haben, während Sie vom Container nur im Kompositionsstamm der Anwendung abhängig sind.

+0

@ user2736022 Sind Sie weiter gekommen, um die aufgetretenen Probleme zu lösen? Hatten meine Antworten irgendeine Hilfe? Wenn ja, vergessen Sie nicht, die Antwort als "Beantwortet" zu markieren! Danke! –

+0

wo kann ich das bekommen? 'SimpleInjectorConfig.Register();' oder um dies einzurichten – Sherlock

+0

@katana SimpleInjectorconfig ist eine Klasse, die das OP selbst erstellt hat. Es ist seine Zusammensetzung Wurzel .... –

7

Zum einen ist dies eine späte Antwort. Ich habe das einfach niedergeschrieben, falls jemand anderes auf das ähnliche Problem stoßen sollte und irgendwie auf diese Seite (wie ich) in Zukunft verlinkt werden soll.

Die vorherige Antwort ist vernünftig, wird aber das Problem nicht lösen, wenn der Dienst tatsächlich per Web-API-Anfrage registriert wird, was normalerweise Leute tun, wenn sie Abhängigkeitsinjektion für Identitätsframework-Objekt wie UserManager verwenden möchten.

Das Problem ist, wenn GrantResourceOwnerCredentials aufgerufen wird (in der Regel, wenn Menschen den "Token" Endpunkt treffen), einfache Injektor startet nicht einen API-Anfrage-Lebenszyklus. Um dies zu lösen, müssen Sie nur einen starten.

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 
    { 
     //...... 
     using (Startup.Container.BeginExecutionContextScope()) 
     { 
      var userService= Startup.Container.GetInstance<IUserService>(); 
      // do your things with userService.. 
     } 
     //..... 
    } 

Mit BeginExecutionContextScope wird einfach Injektor einen neuen Kontext Umfang starten. Denken Sie jedoch daran, dass es explizit entsorgt werden muss.

+0

Dies ist eine nette Lösung. Funktioniert auch in LightInject. –

+1

Dies fügt dem IoC-Framework eine Abhängigkeit hinzu. @Ric. Nets antwort versucht, diese Abhängigkeit zu verbergen, löst jedoch nicht das Problem des Umfangs des Ausführungskontexts. Wie behebt man beide Probleme? – mcanti

+0

@mcanti theoretisch glaube ich, dass Sie Middleware für den Token-Endpunkt hinzufügen können, um einen Start des Lebenszyklus der API-Anfrage zu erzwingen, die ich nicht selbst ausprobieren, nicht sicher, ob es funktioniert oder nicht. – user3682091

0

Solange Sie die Abhängigkeit Resolver für Ihre WebAPI in Ihrem App_Start SimpleInjectorConfig.Register();

Gefällt Ihnen dieses

GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container); 

registrieren Und wenn Sie die AsyncScopedLifestyle Dann empfohlen verwenden, können Sie die Abhängigkeit Resolver verwenden, um Holen Sie sich eine neue Instanz Ihrer Dienste wie diese

using (var scope = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.BeginScope()) 
{ 
    var _userService = scope.GetService(typeof(IUserService)) as IUserService; 
    //your code to use the service 
}