经过两天的测试和尝试,我想出了可行的解决方案。
主要问题是,在 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.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();
}
}
如果有人知道更好的解决方案,我很乐意看到它。