3

更新:不幸的是,Windows 重新启动解决了这个问题 -.-


在我们的 ASP.NET Core (1.0 RC2) 应用程序中,我们有以下要求:只有来自内部网络的用户才能访问一些“调试”页面(由 MVC Core 托管)。这是一个公共网站,我们没有用户登录,而是使用基于自定义 IP 地址的授权来管理它(注意:在我们的案例中,这不是安全风险,因为我们之间有一个代理,所以IP 地址不能从外部欺骗)。

我们也想在 ASP.NET Core 中实现这种基于 IP 地址的授权。我们为此使用自定义策略以及MVC 控制器上的"DebugPages"相应定义。[Authorize(Policy="DebugPages")]然后我们注意到,我们必须有一个经过身份验证的用户才能进入AuthorizeAttribute,我们在请求管道中创建一个,这会产生 Startup.cs 中的以下代码(为简洁起见):

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddAuthorization(options =>
    {
        options.AddPolicy(
            "DebugPages",
            policy => policy.RequireAssertion(
                async context => await MyIPAuthorization.IsAuthorizedAsync()));
    });
}

public void Configure(IApplicationBuilder app)
{
    ...

    app.Use(async (context, next) =>
    {
        context.User = new ClaimsPrincipal(new GenericIdentity("anonymous"));
        await next.Invoke();
    });

    ...
}

现在,当在 Visual Studio 2015(使用 IIS Express)的调试中运行时,这可以正常工作。但不幸的是,当(使用 Kestrel)从命令行直接运行时,它不起作用。dotnet run在这种情况下,我们得到以下异常:

InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic

当我们提供当前的 Windows 主体而不是具有自定义匿名身份的主体时,会发生同样的错误——所以每次当用户自动进行身份验证时......

那么,为什么在 IIS Express 和 Kestrel 中托管存在差异?任何建议如何解决这个问题?

4

1 回答 1

2

因此,经过一些研究,正如我在评论中提到的,当您在“自托管”红隼下启动应用程序时,我发现 httpContext.Authentication.HttpAuthhenticationFeature.Handler 为空。但是当您使用 IIS 时,处理程序已由 Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler 实例化。这个特定的处理程序实现是 Program.cs 中 .UseIISIntegration() 的一部分。

因此,我决定在我的应用程序中使用此实现的一部分并处理未经身份验证的请求。

对于我的 WebAPI(没有任何视图)服务,我使用在幕后使用 OAuth2IntrospectionAuthentication 和 JwtBearerAuthentication 的 IdentityServer4.AccessTokenValidation。

创建文件

KestrelAuthenticationMiddleware.cs

public class KestrelAuthenticationMiddleware
{
    private readonly RequestDelegate _next;
    public KestrelAuthenticationMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task Invoke(HttpContext context)
    {
        var existingPrincipal = context.Features.Get<IHttpAuthenticationFeature>()?.User;
        var handler = new KestrelAuthHandler(context, existingPrincipal);
        AttachAuthenticationHandler(handler);
        try
        {
            await _next(context);
        }
        finally
        {
            DetachAuthenticationhandler(handler);
        }
    }

    private void AttachAuthenticationHandler(KestrelAuthHandler handler)
    {
        var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
        if (auth == null)
        {
            auth = new HttpAuthenticationFeature();
            handler.HttpContext.Features.Set(auth);
        }
        handler.PriorHandler = auth.Handler;
        auth.Handler = handler;
    }

    private void DetachAuthenticationhandler(KestrelAuthHandler handler)
    {
        var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
        if (auth != null)
        {
            auth.Handler = handler.PriorHandler;
        }
    }
}

KestrelAuthHandler.cs

internal class KestrelAuthHandler : IAuthenticationHandler
{
    internal KestrelAuthHandler(HttpContext httpContext, ClaimsPrincipal user)
    {
        HttpContext = httpContext;
        User = user;
    }

    internal HttpContext HttpContext { get; }

    internal ClaimsPrincipal User { get; }

    internal IAuthenticationHandler PriorHandler { get; set; }

    public Task AuthenticateAsync(AuthenticateContext context)
    {
        if (User != null)
        {
            context.Authenticated(User, properties: null, description: null);
        }
        else
        {
            context.NotAuthenticated();
        }


        if (PriorHandler != null)
        {
            return PriorHandler.AuthenticateAsync(context);
        }

        return Task.FromResult(0);
    }

    public Task ChallengeAsync(ChallengeContext context)
    {
        bool handled = false;
        switch (context.Behavior)
        {
            case ChallengeBehavior.Automatic:
                // If there is a principal already, invoke the forbidden code path
                if (User == null)
                {
                    goto case ChallengeBehavior.Unauthorized;
                }
                else
                {
                    goto case ChallengeBehavior.Forbidden;
                }
            case ChallengeBehavior.Unauthorized:
                HttpContext.Response.StatusCode = 401;
                // We would normally set the www-authenticate header here, but IIS does that for us.
                break;
            case ChallengeBehavior.Forbidden:
                HttpContext.Response.StatusCode = 403;
                handled = true; // No other handlers need to consider this challenge.
                break;
        }
        context.Accept();


        if (!handled && PriorHandler != null)
        {
            return PriorHandler.ChallengeAsync(context);
        }

        return Task.FromResult(0);
    }

    public void GetDescriptions(DescribeSchemesContext context)
    {
        if (PriorHandler != null)
        {
            PriorHandler.GetDescriptions(context);
        }
    }

    public Task SignInAsync(SignInContext context)
    {
        // Not supported, fall through
        if (PriorHandler != null)
        {
            return PriorHandler.SignInAsync(context);
        }

        return Task.FromResult(0);
    }

    public Task SignOutAsync(SignOutContext context)
    {
        // Not supported, fall through
        if (PriorHandler != null)
        {
            return PriorHandler.SignOutAsync(context);
        }

        return Task.FromResult(0);
    }
}

在 Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseMiddleware<KestrelAuthenticationMiddleware>();

        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = Configuration[AppConstants.Authority],
            RequireHttpsMetadata = false,
            AutomaticChallenge = true,
            ScopeName = Configuration[AppConstants.ScopeName],
            ScopeSecret = Configuration[AppConstants.ScopeSecret],
            AutomaticAuthenticate = true
        });
        app.UseMvc();
    }
于 2016-07-20T10:13:31.687 回答