Подтвердить что ты не робот

Инъекция зависимостей (с использованием SimpleInjector) и OAuthAuthorizationServerProvider

Новое для инъекций зависимостей, поэтому это, вероятно, простое дело, но я пробовал и не могу понять, что я использую Simple Injector.

У меня есть WebApi, который отлично использует SimpleInjector, теперь я хотел бы реализовать безопасность с помощью OAuth.

Для этого я начал следовать этому руководству, что очень полезно, но не использует Injection Dependancy

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

    }
}

Как я могу заставить это работать с Injection Dependency? Это должно произойти довольно много и должно быть в состоянии сделать что-то, чтобы справиться с этим. Я уверен, что это что-то простое, но я все еще учусь.

4b9b3361

Ответ 1

Мне потребовалось некоторое время, чтобы узнать, можно ли зарегистрировать OAuthAuthorizationServerOptions в конвейере Owin напрямую с помощью метода app.Use() вместо app.UseOAuthAuthorizationServer(), который является только методом расширения над app.Use(). app.Use() имеет перегрузку, где вы можете зарегистрировать делегат, который вы могли бы использовать для построения OAuthAuthorizationServerOptions.

К сожалению, эта попытка зашла в тупик, потому что кажется, что даже если мы будем использовать делегат для построения, это, скорее всего, будет только однократно вызываться конвейером Owin, что приведет к такому же результату, а именно к экземпляру singleton из OAuthAuthorizationServerOptions и, следовательно, все зависимости этого класса также будут однотонные.

Таким образом, единственное решение, позволяющее сохранить работу, как должно быть, - это вытащить новый экземпляр вашего UserService каждый раз, когда вызывается метод GrantResourceOwnerCredentials().

Но чтобы следовать принципам Simple Injector, было бы плохой дизайн, чтобы поддерживать зависимость от контейнера в классе ApiAuthorizationServerProvider, например показывает исходный код.

Лучший способ сделать это - использовать factory для класса UserService вместо прямого вытягивания его из контейнера. Следующий код показывает пример того, как вы могли это сделать:

Прежде всего, очистите метод Application_Start() в файле global.asax и поместите весь свой код запуска в метод 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(), возвращая полностью сконфигурированный контейнер Simple Injector вызывающему, поэтому его можно использовать напрямую.

Теперь измените конструктор вашего класса ApiAuthorizationServerProvider, поэтому метод factory можно ввести:

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...
    }
}

Таким образом вы получаете новый UserService каждый раз, когда вызывается метод GrantResourceOwnerCredentials(), а полный график зависимостей за классом UserService будет соответствовать срокам жизни, определенным вами в конфигурации Simple Injector, в то время как вы зависите только от контейнера в корне композиции приложения.

Ответ 2

Когда вы начинаете с Injection Dependency, Owin, вероятно, не самый дружелюбный API для начала.

Я заметил эту часть в вашем коде:

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

Возможно, вы делаете это как обходной путь, прежде чем вы узнаете, как использовать конструктор. Но я думаю, что ваш ответ прямо там. OAuthAuthorizationServerProvider - одноэлементный, поэтому ваш IUserService также будет синглтон, и все зависимости этого класса будут одинаковыми.

Вы упомянули, что используете репозиторий в своей пользовательской службе. Вероятно, вы не хотите, чтобы этот репозиторий был singleton, поскольку я полагаю, что этот репозиторий будет использовать какой-то DbContext.

Таким образом, промежуточный ответ может быть решением, которое вы уже сделали. Возможно, есть более элегантное решение, если вы сделаете некоторое исследование того, что делает метод UseOAuthAuthorizationServer. Исходный код Катаны можно найти здесь: исходный код Катаны

Для регистрации других классов идентификации asp.net ссылка в комментарии DSR даст вам хорошую отправную точку.

Ответ 3

Во-первых, это поздний ответ. Я просто написал это на случай, если кто-то другой столкнется с подобной проблемой и каким-то образом связатся с этой страницей (как и я) в будущем.

Предыдущий ответ является разумным, но не решает проблему, если служба фактически зарегистрирована в запросе веб-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 простой инжектор запустит новую область контекста. Однако помните, что его нужно утилизировать явно.

Ответ 4

Пока вы регистрируете преобразователь зависимостей для своего веб-приложения в своем App_Start SimpleInjectorConfig.Register();

Подобно этому

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
}