OAuthAuthorizationServerProvider实现中的Autofac依赖注入

Autofac dependency injection in implementation of OAuthAuthorizationServerProvider

我正在创建一个Web API应用程序,我想使用承载令牌进行用户身份验证。我实现了令牌逻辑,遵循这个帖子,一切看起来都很好。注意:我没有使用ASP.NET标识提供程序。相反,我为它创建了一个自定义用户实体和服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 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());

    }
}

这是SimpleAuthorizationServerProvider类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);

    }
}

调用/token url后,我收到以下错误

No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself

在这个类中有没有使用依赖注入的方法?我正在使用存储库模式来访问我的实体,因此我不认为创建对象上下文的新实例是一个好主意。正确的方法是什么?


我也遇到过类似的问题。

这里的问题是,当您尝试在您的供应商中注入IUserService时,autopac检测到它已注册为InstancePerRequest(使用众所周知的生存期范围标记'AutofacWebRequest',但SimpleAuthorizationServerProvider注册在'root'容器范围中,'AutofacWebRequest'范围不可见。

建议的解决方案是将依赖项注册为InstancePerLifetimeScope。这显然解决了问题,但引入了新的问题。所有依赖项都在'root'范围内注册,这意味着所有请求都具有相同的DbContext和服务实例。史蒂文很好地解释了为什么在请求之间共享DbContext不是一个好主意。

经过更深入的调查,我解决了从OAuthAuthorizationServerProvider类的OwinContext中获取'AutofacWebRequest'并解决了它的服务依赖性,而不是让autofac自动注入。为此,我使用了Autofac.Integration.Owin中的OwinContextExtensions.GetAutofacLifetimeScope()扩展方法,见下面的示例:

1
2
3
4
5
6
7
8
9
10
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>();
    ...
}

我在ConfigureOAuth方法中进行了OAuthAuthorizationServerProvider注册和注射,其方式与劳伦蒂乌·斯塔马特(Laurentiu Stamate)在另一个问题中提出的方法类似,如SingleInstance()。我以同样的方式实现了RefreshTokenProvider

编辑

@Bramvandenbussche,这是我在Startup类中的Configuration方法,在这里您可以看到添加到owin管道中的中间件的顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}


要在SimpleAuthorizationServerProvider中使用依赖注入,您必须像其他类型一样将IOAuthAuthorizationServerProvider注册到autopac容器中。你可以这样做:

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

您还需要将容器传递给ConfigureOAuth方法,并让autopac像这样解析您的实例:

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

如果对象中的属性没有通过外部数据更改,则应始终使用单个实例(例如,您在控制器中设置了一个属性,该属性依赖于数据库中存储的某些信息,在这种情况下,应使用InstancePerRequest)。


我还尝试使用owInContextExtensions.getAutoFacLifeTimeScope来回答@jumuro,这样可以节省一天的时间。不是在运行时注册iuserservice,而是在请求后验证/创建实例服务。

我添加了一些新的答案,因为我还不能发表评论,因为我的名声不好,但添加了额外的指南代码来帮助某人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    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);

    }