ASP.NET Core MVC 2.0 中基于路径的身份验证
Path based authentication in ASP.NET Core MVC 2.0
在 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。在新版本中,身份验证被完全重新设计,在文档中我没有找到任何线索如何做到这一点。
对如何迁移上面的代码有什么想法吗?
经过 2 天的测试和尝试,我想出了可行的解决方案。
主要问题是,在 ASP.NET Core MVC 2.0 中,身份验证方法被注册为服务而不是中间件。
这意味着它们必须在 ConfigureServices
方法中而不是在 Configure
中注册,因此无法在注册时创建分支。
此外,身份验证系统使用 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();
}
}
如果有人知道更好的解决方案,我很乐意看到它。
在 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。在新版本中,身份验证被完全重新设计,在文档中我没有找到任何线索如何做到这一点。 对如何迁移上面的代码有什么想法吗?
经过 2 天的测试和尝试,我想出了可行的解决方案。
主要问题是,在 ASP.NET Core MVC 2.0 中,身份验证方法被注册为服务而不是中间件。
这意味着它们必须在 ConfigureServices
方法中而不是在 Configure
中注册,因此无法在注册时创建分支。
此外,身份验证系统使用 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();
}
}
如果有人知道更好的解决方案,我很乐意看到它。