Autofac dependency injection in implementation of OAuthAuthorizationServerProvider

不问归期 提交于 2019-11-29 01:15:10
jumuro

I have had a similar problem.

The problem here is that when you try to inject IUserService in your provider, Autofac detects that it has been registered as InstancePerRequest (that uses the well-known lifetime scope tag 'AutofacWebRequest') but the SimpleAuthorizationServerProvider is registered in the 'root' container scope where the 'AutofacWebRequest' scope is not visible.

A proposed solution is to register dependencies as InstancePerLifetimeScope. This apparently solved the problem but introduces new ones. All dependencies are registered in the 'root' scope, that implies having the same DbContext and services instances for all the requests. Steven explains very good in this answer why is not a good idea to share the DbContext between requests.

After deeper investigation tasks, I've solved the problem getting the 'AutofacWebRequest' from the OwinContext in the OAuthAuthorizationServerProvider class and resolving the services dependencies from it, instead of letting Autofac to inject them automatically. For this I've used the OwinContextExtensions.GetAutofacLifetimeScope() extension method from Autofac.Integration.Owin, see example below:

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

I've made OAuthAuthorizationServerProvider registration and injection inside ConfigureOAuth method in a similar way than proposed by Laurentiu Stamate in another response to this question, as SingleInstance(). I've implemented RefreshTokenProvider in the same way.

EDIT

@BramVandenbussche, this is my Configuration method in the Startup class, where you can see the order of middlewares added to the OWIN pipeline:

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

To use dependency injection in SimpleAuthorizationServerProvider you have to register IOAuthAuthorizationServerProvider to the Autofac container just like any other type. You can do something like this:

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

You also need to pass the container to the ConfigureOAuth method and let Autofac resolve your instance like this:

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

You should always use single instances if your properties within the object don't change via external data (let's say you have a property which you set in the controller which dependents upon some information stored in the database - in this case you should use InstancePerRequest).

I also tried @jumuro answer using the OwinContextExtensions.GetAutofacLifetimeScope that saves my day. Instead of registering the IUserService at runtime, this answer gives an option on validation/creating the instance service after request.

I added some new answer because I can't comment yet because of my low reputations but added additional guide codes to help someone.

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

    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!