【问题标题】:Dependency Injection (using SimpleInjector) and OAuthAuthorizationServerProvider依赖注入(使用 SimpleInjector)和 OAuthAuthorizationServerProvider
【发布时间】:2014-09-23 14:22:29
【问题描述】:

刚接触依赖注入,所以这可能是一个简单的问题,但我已经尝试过但无法弄清楚,我正在使用简单注入器。

我有一个使用 SimpleInjector 的 WebApi 非常好,现在我想使用 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);

    }
}

如何使用依赖注入来实现这一点?这一定会发生很多,并且必须能够做一些事情来处理它。我确信它很简单,但我仍在学习。

【问题讨论】:

  • 您找到其他解决方案了吗?
  • 我使用来自 github 的 OpenIddict 并且仅将角色名称存储为声明。不存储权限,因为如果更新了某些内容,则更改会立即在服务器上。每个请求我只需要检查一次权限,所以这对我来说很好

标签: c# asp.net asp.net-mvc dependency-injection simple-injector


【解决方案1】:

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

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

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

但是要遵循Simple Injector design principles,在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());
    }
}

请注意我是如何更改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 配置中定义的生命周期,而您仅依赖于容器在应用程序的组合根中。

【讨论】:

  • @user2736022 您在解决遇到的问题方面有没有进一步的进展?我的回答有帮助吗?如果是这样,不要忘记将答案标记为“已回答”!谢谢!
  • 我在哪里可以买到这个? SimpleInjectorConfig.Register(); 或设置此
  • @katana Simpleinjectorconfig 是 OP 自己创建的一个类。这是他的作曲根......
  • 感觉是我遇到过的解决这个问题的最佳方法。谢谢!
【解决方案2】:

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

我注意到您的代码中有这一部分:

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

在您了解如何使用构造函数之前,您可能正在将其作为一种解决方法。但我认为这就是你的答案。 OAuthAuthorizationServerProvider 是单例,因此您的 IUserService 也将是单例,并且此类的所有依赖项也将是单例。

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

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

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

【讨论】:

  • 那我想我误解了之前的答案。我之前提到过这个stackoverflow.com/questions/25997592/…,我猜上面的代码不是答案?正如我所说,我是新手,所以不确定我是否理解你的答案?你能解释一下吗?
  • 您确定链接正确吗?它链接到这篇文章!我从您的问题中复制的代码是很好的中间解决方案,因为您每次调用“GrantResourceOwnerCredentials”方法时都向容器询问新实例。这是一件好事,因为否则在应用程序的整个生命周期中将只有一个用户服务实例。
  • 抱歉,我对 stackoverflow 上的 2 个帖子感到困惑。我以为你在这里回复了另一个帖子 stackoverflow.com/questions/26002866/… 我实际上已经在另一个帖子上更改了此代码
  • 其他中的代码更改没有影响。 container.GetInstance 方法只会被调用一次。然后创建的实例将在应用程序的整个生命周期中存在。包括此 ApiAuthorizationServerProvider 背后的完整对象图(UseService、存储库等)。
【解决方案3】:

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

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

问题是当调用 GrantResourceOwnerCredentials 时(通常是当人们点击“令牌”端点时),简单的注入器不会启动 api 请求生命周期。要解决这个问题,您需要做的就是启动一个。

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

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

【讨论】:

  • 这增加了对 IoC 框架的依赖。 @里克。网络答案试图隐藏这种依赖关系,但没有解决执行上下文范围问题。如何解决这两个问题?
  • @mcanti 理论上我相信您可以为令牌端点添加中间件以强制启动 API 请求生命周期,我自己没有尝试过,不确定它是否会起作用。
【解决方案4】:

只要你在 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
}

【讨论】:

    猜你喜欢
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 2012-03-15
    • 2016-12-01
    • 2017-04-02
    • 2012-02-15
    相关资源
    最近更新 更多