OAuthAuthorizationServerProvider 实现中的 Autofac 依赖注入

Posted

技术标签:

【中文标题】OAuthAuthorizationServerProvider 实现中的 Autofac 依赖注入【英文标题】:Autofac dependency injection in implementation of OAuthAuthorizationServerProvider 【发布时间】:2014-11-10 08:35:07 【问题描述】:

我正在创建一个 Web Api 应用程序,并且我想使用不记名令牌进行用户身份验证。 我按照this post 实现了令牌逻辑,一切似乎都运行良好。 注意:我没有使用 ASP.NET 身份提供程序。相反,我为它创建了一个自定义用户实体和服务。

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

    

调用 /token url 后,我收到以下错误

从请求实例的范围中看不到带有与“AutofacWebRequest”匹配的标记的范围。这通常表明注册为 per-HTTP 请求的组件正在由 SingleInstance() 组件(或类似场景)请求。在 Web 集成下,始终从 DependencyResolver.Current 或 ILifetimeScopeProvider.RequestLifetime 请求依赖项,而不是从容器本身

有没有办法在这个类中使用依赖注入?我正在使用存储库模式来访问我的实体,因此我认为创建对象上下文的新实例不是一个好主意。这样做的正确方法是什么?

【问题讨论】:

您找到解决方案了吗?我有同样的问题,无法...谢谢。 @shenku:我添加了对我有用的回复。我希望它有所帮助。 【参考方案1】:

要在 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)。

【讨论】:

那么你可以使用private readonly IAuthService _authService; public SimpleAuthorizationServerProvider(IAuthService authService) _authService = authService; @ShalomDahan 看看这个页面:autofaccn.readthedocs.io/en/latest/resolve【参考方案2】:

我也遇到过类似的问题。

这里的问题是,当您尝试在提供程序中注入 IUserService 时,Autofac 检测到它已注册为 InstancePerRequest(使用众所周知的生命周期范围标记 'AutofacWebRequest')但 SimpleAuthorizationServerProvider'AutofacWebRequest' 范围不可见的'root' 容器范围中注册。

建议的解决方案是将依赖项注册为InstancePerLifetimeScope。这显然解决了问题,但引入了新问题。所有依赖项都在'root' 范围内注册,这意味着所有请求都具有相同的DbContext 和服务实例。 Steven 在这个 answer 中很好地解释了为什么在请求之间共享 DbContext 不是一个好主意。

经过更深入的调查任务后,我已经解决了从 OAuthAuthorizationServerProvider 类中的 OwinContext 获取 'AutofacWebRequest' 并从中解决服务依赖关系的问题,而不是让 Autofac 自动注入它们。为此,我使用了来自Autofac.Integration.OwinOwinContextExtensions.GetAutofacLifetimeScope() 扩展方法,请参见下面的示例:

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

我在ConfigureOAuth 方法中进行了OAuthAuthorizationServerProvider 注册和注入,其方式与Laurentiu Stamate 在another response 中针对此问题提出的方法类似,如SingleInstance()。 我以同样的方式实现了RefreshTokenProvider

编辑

@BramVandenbussche,这是我在Startup 类中的Configuration 方法,您可以在其中看到添加到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);

【讨论】:

OwinContextExtensions.GetAutofacLifetimeScope 为我返回 null,您还有其他设置吗? @BramVandenbussche,您的项目在更改调用此方法之前是否正常工作? @BramVandenbussche,你打电话给app.UseAutofacMiddleware(container)吗?正如 Alex Meyer-Gleaves 在他的 posts 之一中指出的那样:“这是必需的,因为除了启用中间件 DI 支持外,这还负责将生命周期范围置于 OWIN 上下文中”。 感谢您的回复。我确实拨打了app.UseAutofacMiddleware(container),但你链接我的文章也帮助我弄清楚我打电话太晚了。我必须在设置 OAuth 之前移动该行并解决该问题。我现在遇到了 A delegate registered to create instances of 'System.Security.Principal.IPrincipal' returned null 异常,但这是因为在 /token 调用的情况下没有 CurrentContext。 @BramVandenbussche,查看我在Startup 类中的Configuration() 方法,您可以在其中查看添加到OWIN 管道的中间件的顺序。由于我无法在评论中添加格式代码,我编辑了我的答案。【参考方案3】:

我还使用 OwinContextExtensions.GetAutofacLifetimeScope 尝试了 @jumuro 答案,这可以节省我的时间。此答案不是在运行时注册 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);

    

【讨论】:

以上是关于OAuthAuthorizationServerProvider 实现中的 Autofac 依赖注入的主要内容,如果未能解决你的问题,请参考以下文章