parent
5ce20ebdde
commit
f6c3f9e02a
|
|
@ -1,32 +1,24 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
internal class AuthenticationHandler : IAuthenticationHandler
|
internal class AuthenticationHandler : IAuthenticationHandler
|
||||||
{
|
{
|
||||||
private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN";
|
|
||||||
private static readonly Func<object, Task> ClearUserDelegate = ClearUser;
|
|
||||||
private WindowsPrincipal _user;
|
private WindowsPrincipal _user;
|
||||||
private HttpContext _context;
|
private HttpContext _context;
|
||||||
|
private AuthenticationScheme _scheme;
|
||||||
internal AuthenticationScheme Scheme { get; private set; }
|
|
||||||
|
|
||||||
public Task<AuthenticateResult> AuthenticateAsync()
|
public Task<AuthenticateResult> AuthenticateAsync()
|
||||||
{
|
{
|
||||||
var user = GetUser();
|
if (_user != null)
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
|
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(_user, _scheme.Name)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -34,44 +26,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowsPrincipal GetUser()
|
|
||||||
{
|
|
||||||
if (_user == null)
|
|
||||||
{
|
|
||||||
var tokenHeader = _context.Request.Headers[MSAspNetCoreWinAuthToken];
|
|
||||||
|
|
||||||
int hexHandle;
|
|
||||||
if (!StringValues.IsNullOrEmpty(tokenHeader)
|
|
||||||
&& int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
|
|
||||||
{
|
|
||||||
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
|
|
||||||
var handle = new IntPtr(hexHandle);
|
|
||||||
var winIdentity = new WindowsIdentity(handle, IISDefaults.AuthenticationScheme);
|
|
||||||
|
|
||||||
// WindowsIdentity just duplicated the handle so we need to close the original.
|
|
||||||
NativeMethods.CloseHandle(handle);
|
|
||||||
|
|
||||||
_context.Response.RegisterForDispose(winIdentity);
|
|
||||||
// We don't want loggers accessing a disposed identity.
|
|
||||||
// https://github.com/aspnet/Logging/issues/543#issuecomment-321907828
|
|
||||||
_context.Response.OnCompleted(ClearUserDelegate, _context);
|
|
||||||
_user = new WindowsPrincipal(winIdentity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _user;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task ClearUser(object arg)
|
|
||||||
{
|
|
||||||
var context = (HttpContext)arg;
|
|
||||||
if (context.User is WindowsPrincipal)
|
|
||||||
{
|
|
||||||
context.User = null;
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ChallengeAsync(AuthenticationProperties properties)
|
public Task ChallengeAsync(AuthenticationProperties properties)
|
||||||
{
|
{
|
||||||
// We would normally set the www-authenticate header here, but IIS does that for us.
|
// We would normally set the www-authenticate header here, but IIS does that for us.
|
||||||
|
|
@ -87,8 +41,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
|
|
||||||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
||||||
{
|
{
|
||||||
Scheme = scheme;
|
_scheme = scheme;
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_user = context.Features.Get<WindowsPrincipal>(); // See IISMiddleware
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
@ -21,8 +22,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT";
|
private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT";
|
||||||
private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN";
|
private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN";
|
||||||
private const string MSAspNetCoreEvent = "MS-ASPNETCORE-EVENT";
|
private const string MSAspNetCoreEvent = "MS-ASPNETCORE-EVENT";
|
||||||
|
private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN";
|
||||||
private const string ANCMShutdownEventHeaderValue = "shutdown";
|
private const string ANCMShutdownEventHeaderValue = "shutdown";
|
||||||
private static readonly PathString ANCMRequestPath = new PathString("/iisintegration");
|
private static readonly PathString ANCMRequestPath = new PathString("/iisintegration");
|
||||||
|
private static readonly Func<object, Task> ClearUserDelegate = ClearUser;
|
||||||
|
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IISOptions _options;
|
private readonly IISOptions _options;
|
||||||
|
|
@ -131,10 +134,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
if (_options.ForwardWindowsAuthentication)
|
if (_options.ForwardWindowsAuthentication)
|
||||||
{
|
{
|
||||||
// We must always process and clean up the windows identity, even if we don't assign the User.
|
// We must always process and clean up the windows identity, even if we don't assign the User.
|
||||||
var result = await httpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
|
var user = GetUser(httpContext);
|
||||||
if (result.Succeeded && _options.AutomaticAuthentication)
|
if (user != null)
|
||||||
{
|
{
|
||||||
httpContext.User = result.Principal;
|
// Flow it through to the authentication handler.
|
||||||
|
httpContext.Features.Set(user);
|
||||||
|
|
||||||
|
if (_options.AutomaticAuthentication)
|
||||||
|
{
|
||||||
|
httpContext.User = user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,5 +156,39 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
|
|
||||||
await _next(httpContext);
|
await _next(httpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WindowsPrincipal GetUser(HttpContext context)
|
||||||
|
{
|
||||||
|
var tokenHeader = context.Request.Headers[MSAspNetCoreWinAuthToken];
|
||||||
|
|
||||||
|
if (!StringValues.IsNullOrEmpty(tokenHeader)
|
||||||
|
&& int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hexHandle))
|
||||||
|
{
|
||||||
|
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
|
||||||
|
var handle = new IntPtr(hexHandle);
|
||||||
|
var winIdentity = new WindowsIdentity(handle, IISDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
|
// WindowsIdentity just duplicated the handle so we need to close the original.
|
||||||
|
NativeMethods.CloseHandle(handle);
|
||||||
|
|
||||||
|
context.Response.OnCompleted(ClearUserDelegate, context);
|
||||||
|
context.Response.RegisterForDispose(winIdentity);
|
||||||
|
return new WindowsPrincipal(winIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task ClearUser(object arg)
|
||||||
|
{
|
||||||
|
var context = (HttpContext)arg;
|
||||||
|
// We don't want loggers accessing a disposed identity.
|
||||||
|
// https://github.com/aspnet/Logging/issues/543#issuecomment-321907828
|
||||||
|
if (context.User is WindowsPrincipal)
|
||||||
|
{
|
||||||
|
context.User = null;
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace ServerComparison.TestSites
|
||||||
|
{
|
||||||
|
public class OneTransformPerRequest : IClaimsTransformation
|
||||||
|
{
|
||||||
|
public OneTransformPerRequest(IHttpContextAccessor contextAccessor)
|
||||||
|
{
|
||||||
|
ContextAccessor = contextAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IHttpContextAccessor ContextAccessor { get; }
|
||||||
|
|
||||||
|
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
|
||||||
|
{
|
||||||
|
var context = ContextAccessor.HttpContext;
|
||||||
|
if (context.Items["Transformed"] != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Transformation ran multiple times.");
|
||||||
|
}
|
||||||
|
context.Items["Transformed"] = true;
|
||||||
|
return Task.FromResult(principal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,8 @@ namespace ServerComparison.TestSites
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
services.AddSingleton<IClaimsTransformation, OneTransformPerRequest>();
|
||||||
if (IsKestrel)
|
if (IsKestrel)
|
||||||
{
|
{
|
||||||
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
|
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue