Я пытаюсь защитить API OData, используя .net Core 2.2 и AspNetCore.OData 7.2.1, с базовым обработчиком аутентификации.Мне нужно обработать URL-адреса нескольких клиентов и извлечь из URI токен, который затем будет использоваться в обработчике авторизации, чтобы определить, авторизован ли пользователь.
Для этого я использую IHttpContextAccessor, но это работает только сстандартное API, а не с OData.
OData не любит EndpointRouting, и мне пришлось отключить его, как показано ниже, но в таком случае как мне получить доступ к RouteData для получения токена клиента?
Есть ли альтернативный подход?Ниже приведен код, который вы можете использовать, чтобы попробовать это.
Startup.cs
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Needed to be able to get RouteData from HttpContext through the IHttpContextAccessor
app.UseEndpointRouting();
// Needed to secure the application using the standard Authorize attribute
app.UseAuthentication();
// OData entity model builder
var builder = new ODataConventionModelBuilder(app.ApplicationServices);
builder.EntitySet<Value>(nameof(Value) + "s");
app.UseMvc();
app.UseOData("odata", "{tenant}/odata", builder.GetEdmModel());
// Alternative configuration which is affected by the same problem
//
// app.UseMvc(routeBuilder =>
// {
// // Map OData routing adding token for the tenant based url
// routeBuilder.MapODataServiceRoute("odata", "{tenant}/odata", builder.GetEdmModel());
//
// // Needed to allow the injection of OData classes
// routeBuilder.EnableDependencyInjection();
// });
}
BasicAuthenticationHandler.cs
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IHttpContextAccessor httpContextAccessor)
: base(options, logger, encoder, clock)
{
_httpContextAccessor = httpContextAccessor;
}
public string GetTenant()
{
var httpContext = _httpContextAccessor?.HttpContext;
var routeData = httpContext?.GetRouteData(); // THIS RESULTS ALWAYS IN NULL ROUTE DATA!
return routeData?.Values["tenant"]?.ToString();
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Missing Authorization Header");
try {
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
var tenant = GetTenant();
if (string.IsNullOrEmpty(tenant))
{
return AuthenticateResult.Fail("Unknown tenant");
}
if(string.IsNullOrEmpty(username) || username != password)
return AuthenticateResult.Fail("Wrong username or password");
}
catch (Exception e)
{
return AuthenticateResult.Fail("Unable to authenticate");
}
var claims = new[] {
new Claim("Tenant", "tenant id")
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.Headers["WWW-Authenticate"] = "Basic realm=\"Oh my OData\", charset=\"UTF-8\"";
await base.HandleChallengeAsync(properties);
}
}
Value.cs
public class Value
{
public int Id { get; set; }
public string Name { get; set; }
}
ValuesController.cs
[Authorize]
public class ValuesController : ODataController
{
private List<Value> _values;
public ValuesController()
{
_values = new List<Value>
{
new Value {Id = 1, Name = "A1"},
new Value {Id = 2, Name = "A2"},
new Value {Id = 3, Name = "A3"},
new Value {Id = 4, Name = "A4"},
new Value {Id = 5, Name = "A5"},
new Value {Id = 6, Name = "A6"},
new Value {Id = 7, Name = "A7"},
new Value {Id = 11, Name = "B1"},
new Value {Id = 12, Name = "B2"},
new Value {Id = 13, Name = "B3"},
new Value {Id = 14, Name = "B4"},
new Value {Id = 15, Name = "B5"},
new Value {Id = 16, Name = "B6"},
new Value {Id = 17, Name = "B7"}
};
}
// GET {tenant}/odata/values
[EnableQuery]
public IQueryable<Value> Get()
{
return _values.AsQueryable();
}
// GET {tenant}/odata/values/5
[EnableQuery]
public ActionResult<Value> Get([FromODataUri] int key)
{
if(_values.Any(v => v.Id == key))
return _values.Single(v => v.Id == key);
return NotFound();
}
}
EDIT: добавлен пример кода в рабочем приложении для воспроизведениярешения проблем и испытаний: https://github.com/norcino/so-58016881-OData-GetRoute