匿名用户授权(自动身份验证)

时间:2016-06-21 07:41:48

标签: c# asp.net-core asp.net-core-1.0 asp.net-authorization asp.net-authentication

更新:不幸的是,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)在Debug中运行时, 正常 。 但遗憾的是,当dotnet run(使用Kestrel)从命令行直接运行时, 无效 。在这种情况下,我们得到以下异常:

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

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

那么,为什么在IIS Express和Kestrel中托管之间有区别?有什么建议如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

因此,经过一些研究,正如我在评论中提到的,当我在" selfhosted"下启动应用程序时,我发现httpContext.Authentication.HttpAuthhenticationFeature.Handler为null。红隼。但是当您使用IIS时,Handler已由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();
    }