15

Я создаю приложение Web Api, и я хочу использовать токены-носители для аутентификации пользователя. Я реализовал логику токена, следуя this post, и все работает нормально. ПРИМЕЧАНИЕ. Я не пользуюсь поставщиком удостоверений ASP.NET. Вместо этого я создал для него пользовательский объект и службы.Запуск зависимостей Autofac в реализации OAuthAuthorizationServerProvider

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

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

    } 
} 

После того как я называю маркером URL /, я получаю ошибку при слежении

Нет сферу с согласованием тегов «AutofacWebRequest 'видно из области, в которой запрашивался экземпляр. Это обычно указывает на то, что компонент, зарегистрированный в качестве HTTP-запроса, запрашивается компонентом SingleInstance() (или аналогичным сценарием). В рамках веб-интеграции всегда запрашиваются зависимости от DependencyResolver.Current или ILifetimeScopeProvider.RequestLifetime, никогда из самого контейнера

Есть ли способ использовать инъекцию зависимостей внутри этого класса? Я использую шаблон репозитория для доступа к моим объектам, поэтому я не думаю, что это хорошая идея создать новый экземпляр контекста объекта. Каков правильный способ сделать это?

+0

Удалось ли вам найти решение этого вопроса? У меня такая же проблема, и я не могу ... спасибо. – shenku

+0

@shenku: Я добавил ответ с тем, что сработало для меня. Я надеюсь, что это помогает. – jumuro

ответ

8

Для использования инъекции зависимостей в SimpleAuthorizationServerProvider вам необходимо зарегистрировать IOAuthAuthorizationServerProvider в контейнере Autofac, как и любой другой тип. Вы можете сделать что-то вроде этого:

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

Вы также должны передать контейнер методу ConfigureOAuth и пусть Autofac решить пример, как это:

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

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

+0

, то вы можете использовать 'private readonly IAuthService _authService; public SimpleAuthorizationServerProvider (IAuthService authService) { _authService = authService; } ' – BotanMan

12

У меня была аналогичная проблема.

Проблема здесь заключается в том, что при попытке внедрить IUserService в поставщика, Autofac обнаруживает, что он был зарегистрирован в качестве InstancePerRequest (который использует хорошо известную пожизненную области видимости метки 'AutofacWebRequest'), но SimpleAuthorizationServerProvider зарегистрирован в рамках 'root' контейнера где область 'AutofacWebRequest' не видна.

Предлагаемое решение заключается в регистрации зависимостей как InstancePerLifetimeScope. Это, видимо, решило проблему, но вводит новые. Все зависимости регистрируются в области 'root', что подразумевает наличие того же DbContext и экземпляров служб для всех запросов. Steven объясняет очень хорошо в этом answer почему не рекомендуется делиться DbContext между запросами.

После глубоких задач расследования, я решал проблемы, получая 'AutofacWebRequest' от OwinContext в OAuthAuthorizationServerProvider классе и разрешающую зависимости услуги от него, вместо того, чтобы позволить Autofac вводить их автоматически.Для этого я использовал метод расширения OwinContextExtensions.GetAutofacLifetimeScope() из Autofac.Integration.Owin, смотрите пример ниже:

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>(); 
    ... 
} 

Я сделал OAuthAuthorizationServerProvider регистрации и инъекции внутри ConfigureOAuth метода подобным образом, чем предложенный Laurentiu Stamate в another response на этот вопрос, так как SingleInstance(). Так же я реализовал RefreshTokenProvider.

EDIT

@BramVandenbussche, это мой Configuration метод в Startup классе, где вы можете увидеть порядок добавлен в промежуточное программное трубопровод Owin:

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); 
} 
+1

OwinContextExtensions.GetAutofacLifetimeScope возвращает null для меня, было ли что-нибудь еще, что вы настроили? –

+0

@BramVandenbussche, был ли ваш проект работать до изменений для вызова этого метода? – jumuro

+1

@BramVandenbussche, вы вызываете 'app.UseAutofacMiddleware (container)'? Как указал Алекс Мейер-Гливес в одном из своих [сообщений] (http://alexmg.com/owin-support-for-the-web-api-2-and-mvc-5-integrations-in-autofac/) : «Это необходимо, так как наряду с поддержкой поддержки промежуточного ПО DI **, это также отвечает за размещение области жизни в контексте OWIN **». – jumuro

1

Я также попытался @jumuro ответьте с помощью OwinContextExtensions.GetAutofacLifetimeScope, который экономит мой день. Вместо того, чтобы регистрировать IUserService во время выполнения, этот ответ дает возможность проверки/создания службы экземпляра после запроса.

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

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

    }