15

Sto creando un'applicazione Web Api e desidero utilizzare i token bearer per l'autenticazione dell'utente. Ho implementato la logica del token, seguendo this post e tutto sembra funzionare correttamente. NOTA: non sto utilizzando il provider di identità ASP.NET. Invece ho creato un'entità utente personalizzata e servizi per questo.Iniezione dipendenza Autofac in implementazione di OAuthAuthorizationServerProvider

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     ConfigureOAuth(app); 

     var config = new HttpConfiguration(); 
     var container = DependancyConfig.Register(); 
     var dependencyResolver = new AutofacWebApiDependencyResolver(container); 
     config.DependencyResolver = dependencyResolver; 

     app.UseAutofacMiddleware(container); 
     app.UseAutofacWebApi(config); 

     WebApiConfig.Register(config); 
     app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 
     app.UseWebApi(config); 
    } 

    public void ConfigureOAuth(IAppBuilder app) 
    { 
     var oAuthServerOptions = new OAuthAuthorizationServerOptions 
     { 
      AllowInsecureHttp = true, 
      TokenEndpointPath = new PathString("/token"), 
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
      Provider = new SimpleAuthorizationServerProvider() 
     }; 

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

    } 
} 

e questa è la mia implementazione della classe SimpleAuthorizationServerProvider

private IUserService _userService; 
    public IUserService UserService 
    { 
     get { return (IUserService)(_userService ?? GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUserService))); } 
     set { _userService = value; } 
    } 

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

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

     var user = await UserService.GetUserByEmailAndPassword(context.UserName, context.Password); 

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

    } 
} 

Dopo che io chiamo l'url/gettone, ricevo il seguente errore

No portata con un corrispondente tag 'AutofacWebRequest 'è visibile dall'ambito in cui è stata richiesta l'istanza. Questo in genere indica che un componente registrato come richiesta HTTP viene richiesto da un componente SingleInstance() o uno scenario simile. Nell'integrazione Web vengono sempre richieste dipendenze da DependencyResolver.Current o ILifetimeScopeProvider.RequestLifetime, mai dal contenitore stesso

C'è un modo per utilizzare l'iniezione di dipendenza all'interno di questa classe? Sto usando un pattern di repository per accedere alle mie entità, quindi non penso che sia una buona idea creare una nuova istanza del contesto dell'oggetto. Qual è il modo corretto per farlo?

+0

Sei riuscito a trovare una soluzione a questo? Ho lo stesso problema e non riesco a ... grazie. – shenku

+0

@shenku: ho aggiunto una risposta con ciò che ha funzionato per me. Spero possa essere d'aiuto. – jumuro

risposta

8

Per utilizzare l'iniezione di dipendenza in SimpleAuthorizationServerProvider, è necessario registrare IOAuthAuthorizationServerProvider nel contenitore Autofac come qualsiasi altro tipo. Si può fare qualcosa di simile:

builder 
    .RegisterType<SimpleAuthorizationServerProvider>() 
    .As<IOAuthAuthorizationServerProvider>() 
    .PropertiesAutowired() // to automatically resolve IUserService 
    .SingleInstance(); // you only need one instance of this provider 

È inoltre necessario passare il contenitore per il metodo ConfigureOAuth e lasciate Autofac risolvere l'istanza in questo modo:

var oAuthServerOptions = new OAuthAuthorizationServerOptions 
{ 
    AllowInsecureHttp = true, 
    TokenEndpointPath = new PathString("/token"), 
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), 
    Provider = container.Resolve<IOAuthAuthorizationServerProvider>() 
}; 

È consigliabile utilizzare sempre singoli casi, se le proprietà all'interno dell'oggetto non cambiano tramite dati esterni (diciamo che hai una proprietà che hai impostato nel controller che dipende da alcune informazioni memorizzate nel database - in questo caso dovresti usare InstancePerRequest).

+0

quindi è possibile utilizzare 'readonly privato IAuthService _authService; public SimpleAuthorizationServerProvider (IAuthService authService) { _authService = authService; } ' – BotanMan

12

Ho avuto un problema simile.

Il problema qui è che quando si tenta di iniettare IUserService nel provider, Autofac rileva che esso è stato registrato come InstancePerRequest (che utilizza il noto tag portata vita 'AutofacWebRequest') ma il SimpleAuthorizationServerProvider è registrata nel campo di applicazione 'root' container dove l'ambito 'AutofacWebRequest' non è visibile.

Una soluzione proposta è registrare le dipendenze come InstancePerLifetimeScope. Questo apparentemente ha risolto il problema, ma ne introduce di nuovi. Tutte le dipendenze sono registrate nello scope 'root', che implica avere le stesse istanze DbContext e servizi per tutte le richieste. Steven spiega molto bene in questo answer perché non è una buona idea condividere lo DbContext tra le richieste.

Dopo le attività di indagine più profonde, ho risolto il problema ricevendo il 'AutofacWebRequest' dal OwinContext nella classe OAuthAuthorizationServerProvider e risolvere le dipendenze dei servizi da esso, invece di lasciare Autofac per iniettare automaticamente.Per questo ho usato il metodo OwinContextExtensions.GetAutofacLifetimeScope() estensione da Autofac.Integration.Owin, vedi esempio qui sotto:

using Autofac.Integration.Owin; 
... 
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) 
{ 
    ... 
    // autofacLifetimeScope is 'AutofacWebRequest' 
    var autofacLifetimeScope = OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext); 
    var userService = autofacLifetimeScope.Resolve<IUserService>(); 
    ... 
} 

Ho fatto OAuthAuthorizationServerProvider registrazione e iniezione all'interno ConfigureOAuth metodo in un modo simile di quanto proposto dalla Laurentiu Stamate in another response a questa domanda, come SingleInstance(). Ho implementato RefreshTokenProvider allo stesso modo.

EDIT

@BramVandenbussche, questo è il mio metodo Configuration nella classe Startup, dove si può vedere l'ordine di middleware aggiunto al gasdotto OWIN:

public void Configuration(IAppBuilder app) 
{ 
    // Configure Autofac 
    var container = ConfigureAutofac(app); 

    // Configure CORS 
    ConfigureCors(app); 

    // Configure Auth 
    ConfigureAuth(app, container); 

    // Configure Web Api 
    ConfigureWebApi(app, container); 
} 
+1

OwinContextExtensions.GetAutofacLifetimeScope restituisce null per me, c'era qualcos'altro che hai impostato? –

+0

@BramVandenbussche, il tuo progetto funzionava prima delle modifiche per chiamare questo metodo? – jumuro

+1

@BramVandenbussche, stai chiamando 'app.UseAutofacMiddleware (container)'? Come indicato da Alex Meyer-Gleaves in uno dei suoi [post] (http://alexmg.com/owin-support-for-the-web-api-2-and-mvc-5-integrations-in-autofac/) : "Questo è necessario perché insieme all'abilitazione del supporto DI del middleware, ** questo è anche responsabile della collocazione dell'ambito della vita nel contesto OWIN **". – jumuro

1

Ho anche provato @jumuro rispondi usando OwinContextExtensions.GetAutofacLifetimeScope che salva la mia giornata. Invece di registrare IUserService in fase di runtime, questa risposta fornisce un'opzione sulla convalida/creazione del servizio di istanza dopo richiesta.

Ho aggiunto qualche nuova risposta perché non posso ancora commentare a causa della mia scarsa reputazione ma ho aggiunto ulteriori codici guida per aiutare qualcuno.

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 
    { 

     try 
     { 
      if (service == null) 
      { 
       var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext); 
       service = scope.Resolve<IUserService>(); 
      } 
      var user = await service.FindUserAsync(context.UserName); 
      if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt)) 
      { 
       context.SetError("invalid_grant", "The user name or password is incorrect."); 
       return; 
      } 
     } 
     catch(Exception ex) 
     { 
      context.SetError("invalid_grant", ex.Message); 
      return; 
     } 

     var identity = new ClaimsIdentity(context.Options.AuthenticationType); 
     identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); 

     AuthenticationProperties properties = CreateProperties(context.UserName); 
     AuthenticationTicket ticket = new AuthenticationTicket(identity, properties); 
     context.Validated(ticket); 
     context.Request.Context.Authentication.SignIn(identity); 

    }