ASP.NET Core MVC 2.0中基于路径的身份验证

问题描述:

在ASP.NET Core MVC 1.1中,我们具有基于路径的身份验证,如下所示:

In ASP.NET Core MVC 1.1 we had path based authentication like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // /api/* path
    app.UseWhen(ctx => IsApiRequest(ctx), subBranch =>
    {
        subBranch.UseApiAuth(GetApiAuthOptions());
    });
    // else
    app.UseWhen(ctx => !IsApiRequest(ctx), subBranch =>
    {
        subBranch.UseOpenIdConnectAuthentication(GetOpenIdOptions());
    });
}

现在,我们希望将其迁移到ASP.NET Core MVC 2.0.在新版本中,身份验证已完全进行了重新设计,而在文档中,我没有找到执行此操作的任何线索. 有什么想法如何迁移上面的代码吗?

Now we want to migrate it to ASP.NET Core MVC 2.0. In new version authentication was completely redesigned, and in docs I didn't find any clue how to do that. Any ideas how to migrate the code above?

经过2天的测试和尝试,我提出了可行的解决方案.

After 2 days of testing and trying, I've came up with working solution.

主要问题是,在ASP.NET Core MVC 2.0中,身份验证方法被注册为服务而不是中间件. 这意味着它们必须在ConfigureServices方法中注册,而不是在Configure中注册,因此无法在注册时进行分支创建分支的方法. 此外,身份验证系统使用AuthenticationOptions确定将使用哪种身份验证方法. 通过测试,我发现AuthenticationOptions实例在请求之间共享,因此无法修改它以调整DefaultScheme. 经过一番挖掘,我发现了IAuthenticationSchemeProvider,可以将其覆盖以克服这些问题.

The main problem is, that in ASP.NET Core MVC 2.0, authentication methods are registred as services rather than middleware. This implies that they must be registered in ConfigureServices method rather than in Configure, so there is no way to branch at registration time to create branches. Moreover, auth system uses AuthenticationOptions to detemine which authentication method will be used. From my testing I discovered, that AuthenticationOptions instance is shared across requests, so it cannot be modified to adjust DefaultScheme. After some digging, I've found IAuthenticationSchemeProvider, which can be overriden to overcome those issues.

这是代码:

// Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    [...]

    // Override default IAuthenticationSchemeProvider implementation
    services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();

    // Register OpenId Authentication services
    services.AddAuthentication().AddCookie(this.GetCookieOptions);
    services.AddAuthentication().AddOpenIdConnect(this.GetOpenIdOptions);

    // Register HMac Authentication services (for API)
    services.AddAuthentication().AddHMac(this.GetHMacOptions);

    [...]
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    [...]

    // /api/* path
    app.UseWhen(ctx => IsApiRequest(ctx), subBranch =>
    {
        // Register middleware which will override DefaultScheme; must be called before UseAuthentication()
        subBranch.UseAuthenticationOverride(HMacAuthenticationDefaults.AuthenticationScheme);
        subBranch.UseAuthentication();
    });
    // else
    app.UseWhen(ctx => !IsApiRequest(ctx), subBranch =>
    {
        // Register middleware which will override DefaultScheme and DefaultChallengeScheme; must be called before UseAuthentication()
        subBranch.UseAuthenticationOverride(new AuthenticationOptions
        {
            DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme,
            DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme
        });
        subBranch.UseAuthentication();
    });

    [...]
}

中间件:

// AuthenticationOverrideMiddleware.cs
// Adds overriden AuthenticationOptions to HttpContext to be used by CustomAuthenticationSchemeProvider
public class AuthenticationOverrideMiddleware
{
    private readonly RequestDelegate _next;
    private readonly AuthenticationOptions _authenticationOptionsOverride;

    public AuthenticationOverrideMiddleware(RequestDelegate next, AuthenticationOptions authenticationOptionsOverride)
    {
        this._next = next;
        this._authenticationOptionsOverride = authenticationOptionsOverride;
    }
    public async Task Invoke(HttpContext context)
    {
        // Add overriden options to HttpContext
        context.Features.Set(this._authenticationOptionsOverride);
        await this._next(context);
    }
}
public static class AuthenticationOverrideMiddlewareExtensions
{
    public static IApplicationBuilder UseAuthenticationOverride(this IApplicationBuilder app, string defaultScheme)
    {
        return app.UseMiddleware<AuthenticationOverrideMiddleware>(new AuthenticationOptions { DefaultScheme = defaultScheme });
    }
    public static IApplicationBuilder UseAuthenticationOverride(this IApplicationBuilder app, AuthenticationOptions authenticationOptionsOverride)
    {
        return app.UseMiddleware<AuthenticationOverrideMiddleware>(authenticationOptionsOverride);
    }
}

CustomAuthenticationSchemeProvider:

CustomAuthenticationSchemeProvider:

// CustomAuthenticationSchemeProvider.cs
// When asked for Default*Scheme, will check in HttpContext for overriden options, and return appropriate schema name
public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
    private readonly IHttpContextAccessor _contextAccessor;

    public CustomAuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IHttpContextAccessor contextAccessor) : base(options)
    {
        this._contextAccessor = contextAccessor;
    }

    // Retrieves overridden options from HttpContext
    private AuthenticationOptions GetOverrideOptions()
    {
        HttpContext context = this._contextAccessor.HttpContext;
        return context?.Features.Get<AuthenticationOptions>();
    }
    public override Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
    {
        AuthenticationOptions overrideOptions = this.GetOverrideOptions();
        string overridenScheme = overrideOptions?.DefaultAuthenticateScheme ?? overrideOptions?.DefaultScheme;
        if (overridenScheme != null)
            return this.GetSchemeAsync(overridenScheme);
        return base.GetDefaultAuthenticateSchemeAsync();
    }
    public override Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
    {
        AuthenticationOptions overrideOptions = this.GetOverrideOptions();
        string overridenScheme = overrideOptions?.DefaultChallengeScheme ?? overrideOptions?.DefaultScheme;
        if (overridenScheme != null)
            return this.GetSchemeAsync(overridenScheme);
        return base.GetDefaultChallengeSchemeAsync();
    }
    public override Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
    {
        AuthenticationOptions overrideOptions = this.GetOverrideOptions();
        string overridenScheme = overrideOptions?.DefaultForbidScheme ?? overrideOptions?.DefaultScheme;
        if (overridenScheme != null)
            return this.GetSchemeAsync(overridenScheme);
        return base.GetDefaultForbidSchemeAsync();
    }
    public override Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
    {
        AuthenticationOptions overrideOptions = this.GetOverrideOptions();
        string overridenScheme = overrideOptions?.DefaultSignInScheme ?? overrideOptions?.DefaultScheme;
        if (overridenScheme != null)
            return this.GetSchemeAsync(overridenScheme);
        return base.GetDefaultSignInSchemeAsync();
    }
    public override Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
    {
        AuthenticationOptions overrideOptions = this.GetOverrideOptions();
        string overridenScheme = overrideOptions?.DefaultSignOutScheme ?? overrideOptions?.DefaultScheme;
        if (overridenScheme != null)
            return this.GetSchemeAsync(overridenScheme);
        return base.GetDefaultSignOutSchemeAsync();
    }
}

如果有人知道更好的解决方案,我很乐意看到它.

If somebody knows better solution, I would love to see it.