You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OWIN中间件中WindowsIdentity.Impersonate()引发异常,是否为中间件问题?

问题描述

我们在Web应用中使用以下OWIN中间件,根据传入请求模拟用户:

public class SomeMiddleware : OwinMiddleware
{
    public SomeMiddleware(OwinMiddleware next) : base(next)

    public override async Task Invoke(IOwinContext context)
    {
        var identity = context.Authentication.User?.Identity;
        
        if(identity is WindowsIdentity windowsIdentity)
        {
            using var ctx = windowsIdentity.Impersonate();
            await Next.Invoke(context);
        }
    }
}

管道中的其他中间件使用log4net记录日志,其附加器布局模板包含用户标识。有时Web应用会抛出System.ObjectDisposedException(安全句柄已关闭)和System.ArgumentException(无效模拟令牌)异常,完整栈追踪如下:

System.ObjectDisposedException: Safe handle has been closed
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
   at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
   at Microsoft.Win32.Win32Native.GetTokenInformation(SafeAccessTokenHandle TokenHandle, UInt32 TokenInformationClass, SafeLocalAllocHandle TokenInformation, UInt32 TokenInformationLength, UInt32& ReturnLength)
   at System.Security.Principal.WindowsIdentity.GetTokenInformation(SafeAccessTokenHandle tokenHandle, TokenInformationClass tokenInformationClass)
   at System.Security.Principal.WindowsIdentity.get_User()
   at System.Security.Principal.WindowsIdentity.GetName()
   at System.Security.Principal.WindowsIdentity.get_Name()
   at log4net.Core.LoggingEvent.get_Identity()
   at log4net.Layout.Pattern.IdentityPatternConverter.Convert(TextWriter writer, LoggingEvent loggingEvent)
   at log4net.Layout.Pattern.PatternLayoutConverter.Convert(TextWriter writer, Object state)
   at log4net.Util.PatternConverter.Format(TextWriter writer, Object state)
   at log4net.Layout.PatternLayout.Format(TextWriter writer, LoggingEvent loggingEvent)
   at log4net.Appender.AppenderSkeleton.RenderLoggingEvent(TextWriter writer, LoggingEvent loggingEvent)
   at log4net.Appender.TextWriterAppender.Append(LoggingEvent loggingEvent)
   at log4net.Appender.FileAppender.Append(LoggingEvent loggingEvent)
   at log4net.Appender.RollingFileAppender.Append(LoggingEvent loggingEvent)
   at log4net.Appender.AppenderSkeleton.DoAppend(LoggingEvent loggingEvent)
...
Invalid token for impersonation - it cannot be duplicated.</Message><StackTrace>   at System.Security.Principal.WindowsIdentity.CreateFromToken(IntPtr userToken)
   at System.Security.Principal.WindowsIdentity..ctor(IntPtr userToken, String authType, Int32 isAuthenticated)
   at System.Security.Principal.WindowsIdentity..ctor(SafeAccessTokenHandle safeTokenHandle)
   at System.Security.SecurityContext.CaptureCore(Reader currThreadEC, StackCrawlMark&amp;amp; stackMark)
   at System.Threading.ExecutionContext.Capture(StackCrawlMark&amp;amp; stackMark, CaptureOptions options)
   at System.Threading.ExecutionContext.FastCapture()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.GetCompletionAction(Task taskForTracing, MoveNextRunner&amp;amp; runnerToInitialize)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AwaitUnsafeOnCompleted[TAwaiter,TStateMachine](TAwaiter&amp;amp; awaiter, TStateMachine&amp;amp; stateMachine)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.&amp;lt;&amp;gt;c.&amp;lt;ThrowAsync&amp;gt;b__6_1(Object state)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()</StackTrace><ExceptionString>System.ArgumentException: Invalid token for impersonation - it cannot be duplicated.
   at System.Security.Principal.WindowsIdentity.CreateFromToken(IntPtr userToken)
   at System.Security.Principal.WindowsIdentity..ctor(IntPtr userToken, String authType, Int32 isAuthenticated)
   at System.Security.Principal.WindowsIdentity..ctor(SafeAccessTokenHandle safeTokenHandle)
   at System.Security.SecurityContext.CaptureCore(Reader currThreadEC, StackCrawlMark&amp;amp; stackMark)
   at System.Threading.ExecutionContext.Capture(StackCrawlMark&amp;amp; stackMark, CaptureOptions options)
   at System.Threading.ExecutionContext.FastCapture()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.GetCompletionAction(Task taskForTracing, MoveNextRunner&amp;amp; runnerToInitialize)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AwaitUnsafeOnCompleted[TAwaiter,TStateMachine](TAwaiter&amp;amp; awaiter, TStateMachine&amp;amp; stateMachine)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.&amp;lt;&amp;gt;c.&amp;lt;ThrowAsync&amp;gt;b__6_1(Object state)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()</ExceptionString></Exception></TraceRecord>
An unhandled exception of type 'System.ArgumentException' occurred in mscorlib.dll

请问该中间件大概率是故障原因吗?我是否找错了排查方向?


问题分析与解决方案

这个中间件确实是导致异常的核心原因,你的排查方向没错,问题出在异步场景下的模拟令牌生命周期管理:

  1. 令牌提前释放问题
    你用using var ctx = windowsIdentity.Impersonate()创建的模拟上下文,会在await Next.Invoke(context)执行完成后立即释放。但异步操作中,后续的日志操作(比如log4net获取用户标识)可能在模拟上下文释放后才执行,此时WindowsIdentity的安全句柄已经被关闭,就会抛出System.ObjectDisposedException。

  2. 令牌无法复制问题
    异步执行时,.NET会捕获ExecutionContext并在线程池线程上恢复,其中包含了模拟的身份令牌。但WindowsIdentity的默认令牌是不可复制的(比如来自IIS的请求令牌),当尝试在另一个线程上使用这个令牌时,就会抛出System.ArgumentException(无效模拟令牌)。

修复方案

方案1:复制令牌并确保模拟上下文覆盖异步操作

复制原令牌以支持跨线程使用,同时确保模拟上下文覆盖所有依赖身份的异步操作:

public override async Task Invoke(IOwinContext context)
{
    var identity = context.Authentication.User?.Identity;
    
    if(identity is WindowsIdentity windowsIdentity)
    {
        // 复制令牌,确保可以跨线程安全使用
        using var duplicatedToken = new SafeAccessTokenHandle(windowsIdentity.Token.DangerousGetHandle());
        using var clonedIdentity = new WindowsIdentity(duplicatedToken.DangerousGetHandle());
        using var ctx = clonedIdentity.Impersonate();
        
        await Next.Invoke(context);
    }
}

方案2:修改log4net布局,绕过Identity模式

修改log4net的布局模板,不再使用%identity,而是从OWIN上下文手动获取用户标识并传入日志:

<!-- 原布局可能包含 %identity,替换为自定义字段 -->
<conversionPattern value="%date [%thread] %-5level %logger - %property{UserName} %message%newline" />

然后在中间件中设置日志属性:

log4net.LogicalThreadContext.Properties["UserName"] = context.Authentication.User?.Identity.Name;

方案3:禁用ExecutionContext的身份捕获

如果不需要跨线程保留模拟身份,可以在执行异步操作前禁用身份捕获:

public override async Task Invoke(IOwinContext context)
{
    var identity = context.Authentication.User?.Identity;
    
    if(identity is WindowsIdentity windowsIdentity)
    {
        using var ctx = windowsIdentity.Impersonate();
        // 禁用ExecutionContext捕获身份,避免后续线程尝试使用已释放的令牌
        using (ExecutionContext.SuppressFlow())
        {
            await Next.Invoke(context);
        }
    }
}

内容的提问来源于stack exchange,提问作者Józef Podlecki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:15:40