问题
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());
});
}
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?
回答1:
After 2 days of testing and trying, I've came up with working solution.
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.
Here's the code:
// 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();
});
[...]
}
Middleware:
// 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();
}
}
If somebody knows better solution, I would love to see it.
来源:https://stackoverflow.com/questions/46565243/path-based-authentication-in-asp-net-core-mvc-2-0