. NET Проект Core 3.1 WebApi + Аутентификация NTLM - PullRequest
0 голосов
/ 20 февраля 2020

Я пытаюсь перенести старый OWIN-хостинг WebApi, который использовал подобные вещи

    var listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
    listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;

, в новый. NET Core 3.1 проект. Я читал об Auth

Вот так выглядит файл моего проекта

  <Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
      <TargetFramework>netcoreapp3.1</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="3.1.2" />
      <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" />
      <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
      <PackageReference Include="System.Text.Json" Version="4.7.0" />
    </ItemGroup>
  </Project>

А вот так выглядит мой launchsettings.json

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": false,
    "iisExpress": {
      "applicationUrl": "http://localhost:9600",
      "sslPort": 0
    }
  },
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/audit",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "use64Bit": true
    },
    "Audit.Core": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/audit",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:9600"
    },
    "Azure Dev Spaces": {
      "commandName": "AzureDevSpaces",
      "launchBrowser": true
    }
  }
}

А вот так Startup.cs выглядит как

    using Microsoft.AspNetCore.Authentication.Negotiate;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Server.HttpSys;
    using Microsoft.AspNetCore.Server.IISIntegration;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using NLog;

    namespace xxxx
    {
        public class Startup
        {
            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.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
                services.AddControllers().AddNewtonsoftJson();
            }

            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {

                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }

                app.UseRouting();

                app.UseAuthentication();
                app.UseAuthorization();

                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
        }
    }

А вот Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>()
                .UseHttpSys(options =>
                {
                    options.Authentication.Schemes = AuthenticationSchemes.NTLM;
                    options.EnableResponseCaching = false;
                    options.Authentication.AllowAnonymous = false;
                });
            })
            .ConfigureWebHost(config =>
            {
                 config.UseUrls("http://*:9600");
            });
}

И у меня есть специальный фильтр, который мне нужен, чтобы получить текущие учетные данные пользователей и проверить, что у них есть определенный ActiveDirectory группа, связанная с ним. Основное использование c выглядит следующим образом

    [ApiController]
    [Route("api/some")]
    [ControllerExceptionFilter]
    public class SomeController : ControllerBase
    {
        [HttpPost("add")]
        [ActiveDirectoryAuthorize("SomeGroup")]
        public IActionResult Add([FromBody]SomeEvent s)
        {
            var user = this.HttpContext.User.Identity;
            return Ok("cool");
        }
    }

Где фильтр выглядит следующим образом

    //https://stackoverflow.com/questions/31464359/how-do-you-create-a-custom-authorizeattribute-in-asp-net-core
    public class ActiveDirectoryAuthorizeAttribute : TypeFilterAttribute
    {
        public ActiveDirectoryAuthorizeAttribute(string groupMembership) : base(typeof(ActiveDirectoryAuthorizeFilter))
        {
            Arguments = new object[] { groupMembership };
        }
    }

    public class ActiveDirectoryAuthorizeFilter : IAuthorizationFilter
    {
        private string _groupMembership;
        public ActiveDirectoryAuthorizeFilter(string groupMembership)
        {
            _groupMembership = groupMembership;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            try
            {
                Authenticate(context);
            }
            catch (InvalidWindowsUserException ex)
            {
                HandleUnauthorizedRequest(context);
            }

            catch (Exception ex)
            {
                HandleInternalServerError(context);
            }
        }

        protected void HandleUnauthorizedRequest(AuthorizationFilterContext context)
        {
            context.HttpContext.Response.Headers.Add("WWW-Authenticate", "NTLM");
            context.Result = new ContentResult
            {
                Content = "Unauthorized",
                StatusCode = (int)HttpStatusCode.Unauthorized
            };
        }

        private void HandleInternalServerError(AuthorizationFilterContext context)
        {
            context.HttpContext.Response.Headers.Add("WWW-Authenticate", "NTLM");
            context.Result = new ContentResult
            {
                Content = "Internal Server Error",
                StatusCode = (int)HttpStatusCode.InternalServerError
            };
        }

        private void Authenticate(AuthorizationFilterContext context)
        {
            var identity = context?.HttpContext?.User?.Identity;

            if (identity == null)
            {
                throw new InvalidWindowsUserException("Access denied");
            }

            EnsureAdmin(identity);
        }

        private void EnsureAdmin(IIdentity identity)
        {
            ......
        }
    }

Так что при всем этом я могу запустить POSTMAN и выдать NTLM-запрос с ПЛОХОЙ пароль, я получаю 401. Что ожидается

enter image description here

Итак, я редактирую запрос POSTMAN, чтобы вставить правильное пароль, и я получаю это, где я получаю 200 и получаю «крутой» ответ от контроллера, показанного выше

enter image description here

Все работает так, как ожидалось, так далеко. Но если я затем изменю текущий рабочий запрос POSTMAN (200 ok), чтобы снова использовать BAD NTLM пароль. Я ожидал увидеть 401, но вместо этого текущий пользователь просто отображается как авторизованный в моем пользовательском фильтре

enter image description here, и я на самом деле получаю 200 OK

enter image description here

Это поведение отличается от WebApi на основе OLD OWIN. Который распознал следующую последовательность

  1. Неверный пароль NTLM, 401 Несанкционированный
  2. Хороший пароль NTLM, 200 OK
  3. Неверный пароль NTLM, 401 Несанкционированный

Есть ли что-то еще, что мне нужно установить где-нибудь? У кого-нибудь есть какие-нибудь подсказки по этому поводу?

...