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