依赖项注入(使用SimpleInjector)和OAuthAuthorizationServerProvider

本文关键字:OAuthAuthorizationServerProvider SimpleInjector 使用 注入 依赖 | 更新日期: 2023-09-27 18:24:00

依赖注入的新手,所以这可能是一件简单的事情,但我已经尝试过了,但无法解决,我使用的是simple Injector。

我有一个WebApi,它使用SimpleInjector非常好,现在我想使用OAuth实现安全性。

为了做到这一点,我开始遵循本教程,这很有帮助,但没有使用依赖性注入

http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

我有我的global.asax文件,看起来像这样,以设置依赖性注入(完美工作)

protected void Application_Start()
{
    SimpleInjectorConfig.Register();
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

我已经创建了一个Startup.Auth.cs文件来配置OAuth

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new MyAuthorizationServerProvider() // here is the problem
        };
        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

现在,正如我在上面评论的那样,MyAuthorizationServerProvider就是问题所在。它需要一个IUserService的参数,我通常会注入这个参数。我不想清空构造函数,因为我的IUserService还注入了一个存储库。这是文件

public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    private IUserService _service;
    public ApiAuthorizationServerProvider (IUserService service) 
    {
         _service = service;
    }
    public override async Task ValidateClientAuthentication(
        OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }
    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", 
            new[] { "*" });
        IUserService service = Startup.Container.GetInstance<IUserService>();
        User user = _service.Query(e => e.Email.Equals(context.UserName) &&
            e.Password.Equals(context.Password)).FirstOrDefault();
        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);
    }
}

如何使用依赖项注入实现此功能?这种情况肯定经常发生,必须能够做点什么来处理。我相信这很简单,但我仍在学习。

依赖项注入(使用SimpleInjector)和OAuthAuthorizationServerProvider

我花了一些时间来确定是否可以直接使用app.Use()方法在Owin管道中注册OAuthAuthorizationServerOptions,而不是app.UseOAuthAuthorizationServer(),后者只是app.Use()的扩展方法。app.Use()有一个重载,您可以在其中注册一个可用于构造OAuthAuthorizationServerOptions的委托。

不幸的是,这项工作遇到了死胡同,因为似乎即使我们使用委托进行构建,Owin管道也很可能只调用一次,这会导致相同的结果,即OAuthAuthorizationServerOptions的单例实例,因此该类的所有依赖项也将是单例的。

因此,保持事物正常工作的唯一解决方案是,每次调用GrantResourceOwnerCredentials()方法时,都拉取一个UserService的新实例。

但是,为了遵循SimpleInjector的设计原则,在ApiAuthorizationServerProvider类中保持对容器的依赖性将是糟糕的设计,就像原始代码所示的那样。

更好的方法是使用UserService类的工厂,而不是直接从容器中提取它。下一段代码展示了如何做到这一点的示例:

首先,清除global.asax文件中的Application_Start()方法,并将所有启动代码放在Owin Startup()方法中。Startup()方法的代码:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var container = SimpleInjectorConfig.Register();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        Func<IUserService> userServiceFactory = () => 
              container.GetInstance<IUserService>();
        var OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new ApiAuthorizationServerProvider(userServiceFactory)
        };
        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

请注意,我是如何通过将配置完整的SimpleInjector容器返回给调用者来更改SimpleInjectorConfig.Register()函数的签名的,以便可以直接使用它。

现在更改ApiAuthorizationServerProvider类的构造函数,这样就可以注入工厂方法:

public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    private Func<IUserService> userServiceFactory;
    public ApiAuthorizationServerProvider(Func<IUserService> userServiceFactory)
    {
        this.userServiceFactory = userServiceFactory;
    }
    // other code deleted for brevity...
    private IUserService userService 
    { 
        get
        {
            return this.userServiceFactory.Invoke();
        }
    }
    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        // other code deleted for brevity...
        // Just use the service like this
        User user = this.userService.Query(e => e.Email.Equals(context.UserName) &&
            e.Password.Equals(context.Password)).FirstOrDefault();
        // other code deleted for brevity...
    }
}

这样,每次调用GrantResourceOwnerCredentials()方法时都会得到一个新的UserService,并且UserService类后面的完整依赖关系图将遵循您在Simple Injector配置中定义的生存期,而您只依赖于应用程序的组合根中的容器。

当您开始依赖注入时,Owin可能不是最友好的API。

我在你的代码中注意到了这一部分:

IUserService service = Startup.Container.GetInstance<IUserService>();

在了解如何使用构造函数之前,您可能会将此操作作为一种变通方法。但我认为这就是你的答案。OAuthAuthorizationServerProvider是一个singleton,因此您的IUserService也将是一个sington,并且该类的所有依赖项也将是singleton。

您提到您在用户服务中使用存储库。您可能不希望这个存储库是单例的,因为我认为这个存储库将使用某种DbContext。

因此,中间答案可能是您已经制定的解决方案。如果你研究一下UseOAuthAuthorizationServer方法的确切作用,也许会有一个更优雅的解决方案。Katana的源代码可以在这里找到:Katana源代码

对于其他asp.net身份类的注册,DSR注释中的链接将为您提供一个良好的起点。

首先,这是一个迟来的答案。我只是写下了这篇文章,以防其他人将来遇到类似的问题,并以某种方式(像我一样)链接到这个页面。

前面的答案是合理的,但如果服务实际上是根据Web API请求注册的,则不会解决问题,我相信,如果人们想对像UserManager这样的标识框架对象使用依赖项注入,通常会这样做。

问题是,当GrantResourceOwnerCredentials被调用时(通常是当人们到达"token"端点时),简单的注入器不会启动api请求生命周期。要解决这个问题,你所需要做的就是启动一个。

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        //......
        using (Startup.Container.BeginExecutionContextScope())
        {
            var userService= Startup.Container.GetInstance<IUserService>();
            // do your things with userService..
        }
       //.....
    }

使用BeginExecutionContextScope,简单注入器将启动一个新的上下文范围。但是,请记住,它需要明确地处理。

只要您在App_Start SimpleInjectorConfig.Register(); 中为您的webapi注册依赖解析程序

像这个

GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);

如果您正在使用推荐的AsyncScopedLifestyle然后,您可以使用依赖解析程序来获得像这样的服务的新实例

using (var scope = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.BeginScope())
{
    var _userService = scope.GetService(typeof(IUserService)) as IUserService;
    //your code to use the service
}
相关文章:
  • 没有找到相关文章