.NET Core 3.1 WebApi 项目 + NTLM 认证
.NET Core 3.1 WebApi project + NTLM Authentication
我正在尝试迁移使用此类东西的旧 OWIN 自托管 WebApi
var listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
到新的 .NET Core 3.1 项目。我读过 Auth
- here
- here
- here
这是我的项目文件的样子
<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 组。基本用法是这样的
[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");
}
}
过滤器看起来像这样
//
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。这是预期的
然后我编辑 POSTMAN 请求以输入正确的密码,我得到了这个,在那里我得到了 200 并从上面显示的控制器得到了 "cool" 响应
到目前为止一切都按预期工作。但是,如果我随后更改当前工作的 POSTMAN (200 ok) 请求以再次使用 BAD NTLM 密码。我期待看到 401,但当前用户只是在我的自定义过滤器中显示为仍被授权
我实际上得到了 200 OK
此行为不同于旧的基于 OWIN 的 WebApi。哪个确实识别了以下序列
- NTLM 密码错误,401 未经授权
- NTLM 密码正确,200 OK
- NTLM 密码错误,401 未经授权
我还需要在什么地方设置其他东西吗?有人对此有任何线索吗?
我遇到过同样的情况,我觉得是 Postman 的问题,比如 'cached' 好的密码,即使你改错了也一直在发送。
如果退出 re-enter Postman,并使用错误的密码重复上次请求,您将得到 'correct' 401 Unauthorized,即:
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 认证方案有关,这意味着 'challenge',即在幕后有一个 'negotiation',有几个 Postman 透明处理的 HTTP 调用,但有些东西改密码会出错
编辑:值得一提的是,Postman 中的 NTLM 身份验证功能目前处于测试阶段
我正在尝试迁移使用此类东西的旧 OWIN 自托管 WebApi
var listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
到新的 .NET Core 3.1 项目。我读过 Auth
- here
- here
- here
这是我的项目文件的样子
<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 组。基本用法是这样的
[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");
}
}
过滤器看起来像这样
//
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。这是预期的
然后我编辑 POSTMAN 请求以输入正确的密码,我得到了这个,在那里我得到了 200 并从上面显示的控制器得到了 "cool" 响应
到目前为止一切都按预期工作。但是,如果我随后更改当前工作的 POSTMAN (200 ok) 请求以再次使用 BAD NTLM 密码。我期待看到 401,但当前用户只是在我的自定义过滤器中显示为仍被授权
此行为不同于旧的基于 OWIN 的 WebApi。哪个确实识别了以下序列
- NTLM 密码错误,401 未经授权
- NTLM 密码正确,200 OK
- NTLM 密码错误,401 未经授权
我还需要在什么地方设置其他东西吗?有人对此有任何线索吗?
我遇到过同样的情况,我觉得是 Postman 的问题,比如 'cached' 好的密码,即使你改错了也一直在发送。
如果退出 re-enter Postman,并使用错误的密码重复上次请求,您将得到 'correct' 401 Unauthorized,即:
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 认证方案有关,这意味着 'challenge',即在幕后有一个 'negotiation',有几个 Postman 透明处理的 HTTP 调用,但有些东西改密码会出错
编辑:值得一提的是,Postman 中的 NTLM 身份验证功能目前处于测试阶段