17

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

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

Для этого я начал читать этот учебник, который очень полезен, но оленья кожи использовать зависимости Injection

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

    } 
} 

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

+1

Надеюсь, это поможет https: //simpleinjector.codeplex. com/discussion/564822 – DSR

+0

Вы нашли другое решение? – moyomeh

+0

Я использую OpenIddict из github и сохраняю только имя роли в качестве претензии. Разрешения не сохраняются, потому что, если что-то обновляется, изменение происходит немедленно на сервере. Мне нужно только проверять разрешения один раз за запрос, так что это нормально для меня atm – Gillardo

ответ

8

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

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

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

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

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

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

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

+0

Тогда я думаю, что неправильно понял предыдущий ответ. Я привязал это ранее http://stackoverflow.com/questions/25997592/dependency-injection-using-simpleinjector-and-oauthauthorizationserverprovider, и я думаю, что код выше не был ответом? Как я сказал, я новичок, поэтому не уверен, что понимаю ур ответ? Можете ли вы объяснить немного больше, пожалуйста? – Gillardo

+0

Вы уверены, что ссылка верная? Ссылка на этот пост! Код, который я скопировал из вашего вопроса, является хорошим промежуточным решением, поскольку вы запрашиваете контейнер для нового экземпляра каждый раз, когда вызывается метод GrantResourceOwnerCredentials. Это хорошо, потому что в течение всего срока службы приложения будет один и только один экземпляр вашего сервиса. –

+0

Извините, я запутался между двумя сообщениями, которые у меня есть на stackoverflow. Я думал, что u ответил на другой пост здесь. Http://stackoverflow.com/questions/26002866/unable-to-register-api-controller-using-simple-injector. Я действительно изменил этот код на другое сообщение. – Gillardo

23

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

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

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

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

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

Теперь измените конструктор вашего 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... 
    } 
} 

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

+0

@ user2736022 Помогли ли вы решить проблему, с которой вы столкнулись? Были ли мои ответы на любую помощь? Если да, не забудьте отметить ответ «Ответ»! Thanx! –

+0

где я могу это получить? 'SimpleInjectorConfig.Register();' или для установки этого – Sherlock

+0

@katana Simpleinjectorconfig - это класс, созданный самим OP. Это его состав root .... –

7

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

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

+0

Это приятное решение. Также работает в LightInject. –

+1

Это добавляет зависимость от структуры IoC. @Ric. Ответы Nets пытаются скрыть эту зависимость, но не решают проблему области контекста выполнения. Как исправить обе проблемы? – mcanti

+0

@mcanti Теоретически я считаю, что вы можете добавить промежуточное ПО для конечной точки маркера, чтобы заставить запустить жизненный цикл запроса API, который я сам не пробовал, не уверен, что он будет работать или нет. – user3682091

0

Пока вы регистрируете распознаватель зависимостей для вашего WebAPI в вашем 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 
}