services net mvc microsoft iservicecollection inyeccion injection extensions dependencyinjection dependency dependencias asp dependency-injection autofac owin asp.net-web-api2 bearer-token

dependency-injection - net - microsoft extensions dependencyinjection



Autofac inyección de dependencia en la implementación de OAuthAuthorizationServerProvider (3)

He tenido un problema similar.

El problema aquí es que cuando intenta inyectar IUserService en su proveedor, Autofac detecta que se ha registrado como InstancePerRequest (que usa la conocida etiqueta de alcance de por vida ''AutofacWebRequest'' ) pero SimpleAuthorizationServerProvider está registrado en el contenedor de la ''root'' contenedor. donde el alcance ''AutofacWebRequest'' no es visible.

Una solución propuesta es registrar las dependencias como InstancePerLifetimeScope . Esto aparentemente solucionó el problema pero introduce otros nuevos. Todas las dependencias se registran en el ámbito ''root'' , lo que implica tener las mismas instancias de DbContext y servicios para todas las solicitudes. Steven explica muy bien en esta answer por qué no es una buena idea compartir el DbContext entre solicitudes.

Después de las tareas de investigación más profundas, resolví el problema al obtener el ''AutofacWebRequest'' del OwinContext en la clase OAuthAuthorizationServerProvider y a resolver las dependencias de los servicios, en lugar de permitir que Autofac las inyecte automáticamente. Para esto, he usado el método de extensión OwinContextExtensions.GetAutofacLifetimeScope() de Autofac.Integration.Owin , vea el siguiente ejemplo:

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

He hecho el registro e inyección OAuthAuthorizationServerProvider dentro del método ConfigureOAuth de una manera similar a la propuesta por Laurentiu Stamate en otra respuesta a esta pregunta, como SingleInstance() . He implementado RefreshTokenProvider de la misma manera.

EDITAR

@BramVandenbussche, este es mi método de Configuration en la clase de Startup , donde puede ver el orden de los middlewares agregados a la canalización 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); }

Estoy creando una aplicación Web Api y quiero usar tokens de portador para la autenticación del usuario. Implementé la lógica del token, siguiendo esta publicación y todo parece funcionar bien. NOTA: No estoy usando el proveedor de identidad ASP.NET. En su lugar, he creado una entidad de usuario personalizada y servicios para ello.

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

y esta es mi implementación de la clase 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); } }

Después de llamar a la url / token, recibo el siguiente error

Ningún ámbito con una etiqueta que coincida con ''AutofacWebRequest'' es visible desde el ámbito en el que se solicitó la instancia. Esto generalmente indica que un componente registrado como solicitud HTTP está siendo solicitado por un componente SingleInstance () (o un escenario similar). Bajo la integración web, siempre se solicitan dependencias desde DependencyResolver.Current o ILifetimeScopeProvider.RequestLifetime, nunca desde el propio contenedor

¿Hay una manera de usar la inyección de dependencia dentro de esta clase? Estoy usando un patrón de repositorio para acceder a mis entidades, por lo que no creo que sea una buena idea crear una nueva instancia del contexto del objeto. ¿Cuál es la forma correcta de hacer esto?


Para usar la inyección de dependencias en SimpleAuthorizationServerProvider , debe registrar IOAuthAuthorizationServerProvider en el contenedor de Autofac como cualquier otro tipo. Puedes hacer algo como esto:

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

También debe pasar el contenedor al método ConfigureOAuth y dejar que Autofac resuelva su instancia de la siguiente manera:

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

Siempre debe usar instancias únicas si sus propiedades dentro del objeto no cambian a través de datos externos (digamos que tiene una propiedad que usted establece en el controlador que depende de cierta información almacenada en la base de datos, en este caso debe usar InstancePerRequest) .


También probé la respuesta de @jumuro usando OwinContextExtensions.GetAutofacLifetimeScope que me salva el día. En lugar de registrar IUserService en tiempo de ejecución, esta respuesta ofrece una opción en la validación / creación del servicio de instancia después de la solicitud.

Agregué una nueva respuesta porque aún no puedo hacer comentarios debido a mi baja reputación, pero agregue códigos de guía adicionales para ayudar a alguien.

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