2014-09-23 13 views
17

Nowość w Injection Dependency, więc jest to prawdopodobnie prosta sprawa, ale próbowałem i nie mogę tego zrozumieć, używam Simple Injector.Dependency Injection (za pomocą SimpleInjector) i OAuthAuthorizationServerProvider

Mam WebApi, który używa SimpleInjector doskonale dobrze, teraz chciałbym zaimplementować zabezpieczenia przy użyciu OAuth.

Aby to zrobić, zacząłem śledzić ten tutorial, który jest bardzo pomocny, ale nie robi używać dependancy wtryskowa

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

mam pliku global.asax patrząc jak ten, do iniekcji setup dependancy (praca idealny)

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

    GlobalConfiguration.Configure(WebApiConfig.Register); 
} 

Stworzyłem plik Startup.Auth.cs skonfigurować 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()); 
    } 
} 

Jak już pisałem powyżej, problemem jest usługa MyAuthorizationServerProvider. wymaga parametru IUserService, który zwykle wstrzykuję. Nie chcę pustego konstruktora, ponieważ moja IUserService także wstrzykuje repozytorium. Poniżej znajduje się plik

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); 

    } 
} 

Jak mogę to osiągnąć przy pomocy Dependency Injection? To musi się wydarzyć całkiem sporo i musi być w stanie zrobić coś, żeby sobie z tym poradzić. Jestem pewien, że jest to coś prostego, ale wciąż się uczę.

+1

Mam nadzieję, że to pomoże https: //simpleinjector.codeplex. com/discussions/564822 – DSR

+0

znalazłeś jakieś inne rozwiązanie? – moyomeh

+0

Używam OpenIddict z github i przechowuję tylko nazwę roli jako roszczenie. Uprawnienia nie są przechowywane, ponieważ jeśli coś jest aktualizowane, zmiana jest natychmiastowa na serwerze. Muszę tylko sprawdzić uprawnienia raz na żądanie, więc to jest w porządku dla mnie atm – Gillardo

Odpowiedz

8

Kiedy zaczynasz od Dependency Injection, Owin prawdopodobnie nie jest najbardziej przyjaznym API na początek.

zauważyłem tę część w kodzie:

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

Prawdopodobnie robi to jako obejście, aby dowiedzieć się, jak użyć konstruktora. Ale myślę, że to twoja odpowiedź. OAuthAuthorizationServerProvider jest singletonem, więc twoja IUserService będzie także singletonem i wszystkie zależności tej klasy będą również pojedyncze.

Wspomniał Pan, że korzystasz z repozytorium w usłudze użytkownika. Prawdopodobnie nie chcesz, aby to repozytorium było singletonem, ponieważ przypuszczam, że to repozytorium użyje jakiegoś DbContext.

Tak więc odpowiedź pośrednia może być rozwiązaniem, które już stworzyłeś. Być może istnieje bardziej eleganckie rozwiązanie, jeśli zrobisz jakieś badanie na temat tego, co dokładnie robi metoda UseOAuthAuthorizationServer. Kod źródłowy Katana można znaleźć tutaj: Katana source code

Do rejestracji innych klas tożsamości asp.net link w komentarzu DSR dadzą Ci dobry punkt wyjścia.

+0

Wtedy myślę, że źle zrozumiałem poprzednią odpowiedź. Zgadzam się z tym poprzednio http://stackoverflow.com/questions/25997592/dependency-injection-using-simpleinjector-and-oauthauthorizationserverprovider i domyślam się, że powyższy kod nie był odpowiedzią? Jak już powiedziałem, jestem nowy, więc nie jestem pewien, czy rozumiem odpowiedź? Czy możesz wyjaśnić trochę więcej, proszę? – Gillardo

+0

Czy na pewno link jest poprawny? Link do tego posta! Kod, który skopiowałem z twojego pytania, jest dobrym rozwiązaniem pośrednim, ponieważ przy każdym wywołaniu metody "GrantResourceOwnerCredentials" pytasz kontener o nową instancję. Jest to dobre rozwiązanie, ponieważ w przeciwnym razie wystąpi jedno i tylko jedno wystąpienie usługi użytkownika podczas całego okresu użytkowania aplikacji. –

+0

Przepraszam, że mam pomieszane między 2 wpisami, które mam na stackoverflow. Myślałem, że odpowiedziałeś na inny post tutaj http://stackoverflow.com/questions/26002866/unable-to-register-api-controller-using-simple-injector Zmieniłem ten kod w innym poście – Gillardo

23

wziąłem trochę czasu, aby dowiedzieć się, czy byłoby możliwe, aby zarejestrować OAuthAuthorizationServerOptions w pipeling Owin stosując metodę app.Use() bezpośrednio, zamiast app.UseOAuthAuthorizationServer() który jest po prostu metoda rozszerzenie na app.Use(). app.Use() ma przeciążenie, w którym można zarejestrować delegata, którego można użyć do skonstruowania modelu OAuthAuthorizationServerOptions.

Niestety ten wysiłek trafił w ślepy zaułek, ponieważ wydaje się, że nawet jeśli użyjemy delegata do budowy, najprawdopodobniej zostanie to wywołane tylko raz przez rurociąg Owin, co prowadzi do tego samego wyniku, a mianowicie pojedynczego wystąpienie OAuthAuthorizationServerOptions, a tym samym wszystkie zależności tej klasy będą również pojedyncze.

Dlatego jedynym rozwiązaniem, które pozwala zachować działanie tak, jak powinno być, jest pobranie nowej instancji twojego UserService za każdym razem, gdy wywoływana jest metoda GrantResourceOwnerCredentials().

Ale podążanie za Simple Injector design principles byłoby złym projektem, aby zachować zależność od kontenera w klasie ApiAuthorizationServerProvider, jak pokazuje oryginalny kod.

Lepszym sposobem na to byłoby użycie fabryki dla klasy UserService zamiast bezpośredniego wyciągania jej z pojemnika. Następny kod pokazuje przykład, jak można to zrobić:

Przede wszystkim wyczyść metodę Application_Start() w pliku global.asax i umieść cały kod startowy w metodzie Owin Startup(). Kod metody Startup():

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()); 
    } 
} 

Wskazówki jak zmieniłem podpis funkcji SimpleInjectorConfig.Register() zwracając kompletnie skonfigurowany pojemnik Proste wtryskiwacza do rozmówcy, dzięki czemu można go stosować bezpośrednio.

Teraz zmień konstruktora klasy ApiAuthorizationServerProvider, więc metoda fabryka może być wstrzykiwany:

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... 
    } 
} 

W ten sposób można uzyskać nowy UserService każdym razem metoda GrantResourceOwnerCredentials() nazywa i kompletny wykres zależności tył klasy UserService będzie śledzić czasy życia zdefiniowane w konfiguracji Simple Injector, podczas gdy ty będziesz zależał od kontenera tylko w głównym katalogu aplikacji.

+0

@ user2736022 Czy udało Ci się rozwiązać problemy, które napotkałeś? Czy moje odpowiedzi były pomocne? Jeśli tak, nie zapomnij oznaczyć odpowiedzi jako "Odpowiedz"! Thanx! –

+0

gdzie mogę to zdobyć? 'SimpleInjectorConfig.Register();' lub aby ustawić ten – Sherlock

+0

@katana Simpleinjectorconfig jest klasą, którą OP sam stworzył. To jest jego korzeń kompozycji ... –

7

Po pierwsze, jest to późna odpowiedź. Napisałem to na wypadek, gdyby ktoś inny spotkał się z podobnym problemem i jakoś w przyszłości uzyskał link do tej strony (jak ja).

Poprzednia odpowiedź jest uzasadniona, ale nie rozwiąże problemu, jeśli usługa jest rzeczywiście zarejestrowana na żądanie interfejsu API sieci Web, co, jak uważam, zwykle wykonuje osoba, która chce użyć wtyczki zależności dla obiektu struktury tożsamości, np. UserManager.

Problem polega na wywołaniu funkcji GrantResourceOwnerCredentials (zwykle gdy ludzie trafiają na punkt końcowy "token"), prosty wtryskiwacz nie rozpocznie cyklu życia aplikacji. Aby rozwiązać ten problem, wystarczy uruchomić go.

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

Za pomocą BeginExecutionContextScope prosty wtryskiwacz rozpocznie nowy zakres kontekstowy. Należy jednak pamiętać, że należy go wyraźnie przekazać.

+0

To dobre rozwiązanie. Działa również w LightInject. –

+1

Powoduje to dodanie zależności do struktury IoC. @Ric. Odpowiedź sieciowa próbuje ukryć tę zależność, ale nie rozwiązuje problemu zasięgu kontekstu wykonywania. Jak naprawić oba problemy? – mcanti

+0

@mcanti Teoretycznie wierzę, że możesz dodać oprogramowanie pośrednie do punktu końcowego tokena, aby wymusić rozpoczęcie cyklu życia żądania API, którego sam nie wypróbowałem, nie wiem, czy to zadziała, czy nie. – user3682091

0

Dopóki jesteś rejestracji resolwer zależnościach dla WebAPI w App_Start SimpleInjectorConfig.Register();

Ci się to

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

A jeśli używasz zalecana AsyncScopedLifestyle Następnie można użyć rozpoznawania nazw zależność do pobierz nowe wystąpienie z usługami, takimi jak ten

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