首页 文章

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

提问于
浏览
2

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

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. 在新版本中,身份验证完全重新设计,在文档中我没有找到任何线索如何做到这一点。有任何想法如何迁移上面的代码?

1 回答

  • 4

    经过 2 天的测试和尝试,我找到了工作解决方案。

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

    这是代码:

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

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

相关问题