Внедрение зависимостей Autofac в реализации OAuthAuthorizationServerProvider
Я создаю приложение Web Api, и я хочу использовать токены-носители для аутентификации пользователя.
Я реализовал логику токенов, следуя этой статье, и все работает нормально.
ПРИМЕЧАНИЕ. Я не использую ASP.NET Identity Provider. Вместо этого я создал для него пользовательский объект и службы.
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, никогда из самого контейнера
Есть ли способ использовать инъекцию зависимостей внутри этого класса? Я использую шаблон репозитория для доступа к моим объектам, поэтому я не думаю, что это хорошая идея создать новый экземпляр контекста объекта. Каков правильный способ сделать это?
Ответы
Ответ 1
У меня была аналогичная проблема.
Проблема заключается в том, что при попытке ввести IUserService
у вашего провайдера Autofac обнаруживает, что он был зарегистрирован как InstancePerRequest
(который использует известный тег области видимости времени 'AutofacWebRequest'
), но SimpleAuthorizationServerProvider
зарегистрирован в области контейнера 'root'
, где область 'AutofacWebRequest'
не отображается.
Предлагаемое решение заключается в регистрации зависимостей как InstancePerLifetimeScope
. Это, видимо, решило проблему, но вводит новые. Все зависимости регистрируются в области 'root'
, что подразумевает наличие тех же DbContext
и экземпляров служб для всех запросов. Steven объясняет очень хорошее в этом ответе, почему не рекомендуется делиться 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 в другой ответ на этот вопрос, как SingleInstance()
.
Я реализовал RefreshTokenProvider
таким же образом.
ИЗМЕНИТЬ
@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);
}
Ответ 2
Чтобы использовать инъекцию зависимостей в 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).
Ответ 3
Я также попробовал ответить @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);
}