.NET Core 3.1 WebApi项目+ NTLM身份验证

时间:2020-02-20 16:17:35

标签: asp.net-core postman asp.net-core-webapi asp.net-core-3.1

我正在尝试迁移使用这种东西的旧的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");
            });
}

我有一个自定义过滤器,我需要从中获取当前用户的Identity并验证它是否具有与之关联的特定ActiveDirectory组。基本用法是这样的

    [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并使用 BAD 密码发出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

此行为与基于OLD OWIN的WebApi不同。哪个确实可以识别以下顺序

  1. NTLM错误密码,未授权401
  2. NTLM好的密码,输入200 OK
  3. NTLM错误密码,未授权401

我还需要在某处设置其他内容吗?有人对此有任何线索吗?

1 个答案:

答案 0 :(得分:1)

我遇到过同样的情况,在我看来,这是一个邮递员问题,就像它“缓存”了好的密码,并且即使您用错误的密码更改了密码一样,也继续发送。

如果您退出并重新进入邮递员,并使用错误的密码重复上一次请求,您将获得“正确的” 401未经授权的信息,即:

 1. NTLM bad password              -> 401 Unauthorized - correct
 2. NTLM good password             -> 200 OK           - correct
 3. NTLM bad password              -> 200 OK           - WRONG, as if Postman cached the good password
 4. Exit Postman - Re-enter Postman
 5. NTLM bad password (same as 3.) -> 401 Unauthorized - correct

也许它与NTLM认证机制有关,这暗示着“挑战”,即幕后有一个“谈判”,而Postman透明地处理了多个HTTP调用,但是如果您更改了密码。

编辑:值得一提的是,邮递员中的NTLM身份验证功能当前位于测试版中。