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

C# COM对象如何获取C++调用者令牌并授予文件读写权限?

在C# COM对象中获取C++调用者的安全令牌并设置文件ACL

核心思路

COM调用依赖RPC机制,可通过Windows API获取调用者的安全上下文:先通过CoGetCallContext获取IServerSecurity接口,调用其ImpersonateClient方法模拟调用者身份,再获取当前线程的安全令牌;从令牌提取用户SID后,为新建文件添加该SID的读写权限,避免授予EVERYONE权限。

步骤1:导入必要的Windows API与COM接口

在C#中需要定义IServerSecurity接口(COM内置安全接口)并导入相关系统API:

using System;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.IO;
using System.ComponentModel;

[ComImport, Guid("0000013E-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IServerSecurity
{
    void QueryBlanket(
        out uint pAuthnLevel,
        out uint pImpLevel,
        out IntPtr pPrivs,
        out uint pAuthnSvc,
        out IntPtr pAuthzSvc,
        out IntPtr pAuthInfo,
        out uint pCapabilities);
    void ImpersonateClient();
    void RevertToSelf();
    [PreserveSig]
    int IsImpersonating();
}

// 导入系统API
[DllImport("ole32.dll")]
private static extern int CoGetCallContext(ref Guid riid, out IntPtr ppv);

[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentThread();

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenThreadToken(IntPtr ThreadHandle, uint DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

// 令牌访问权限常量
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;

步骤2:实现获取调用者令牌并设置文件ACL的逻辑

在COM对象的方法中,按流程获取令牌、创建文件并设置权限:

public string CreateRestrictedFile(string filePath)
{
    Guid iidIServerSecurity = typeof(IServerSecurity).GUID;
    IntPtr pServerSecurity = IntPtr.Zero;
    IServerSecurity serverSecurity = null;
    IntPtr callerToken = IntPtr.Zero;

    try
    {
        // 获取COM调用上下文的安全接口
        int hr = CoGetCallContext(ref iidIServerSecurity, out pServerSecurity);
        if (hr != 0)
            throw new COMException("Failed to get call context", hr);

        serverSecurity = (IServerSecurity)Marshal.GetObjectForIUnknown(pServerSecurity);

        // 模拟调用者身份
        serverSecurity.ImpersonateClient();
        try
        {
            // 获取当前线程的令牌(即调用者的令牌)
            if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE, false, out callerToken))
            {
                // 线程无令牌时尝试获取进程令牌(兼容低模拟级别场景)
                if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, out callerToken))
                    throw new Win32Exception();
            }
        }
        finally
        {
            // 恢复COM对象自身身份
            serverSecurity.RevertToSelf();
        }

        // 创建目标文件
        using (FileStream fs = File.Create(filePath)) {}

        // 从令牌提取调用者的SID
        using (WindowsIdentity callerIdentity = new WindowsIdentity(callerToken))
        {
            SecurityIdentifier callerSid = callerIdentity.User;

            // 构建文件ACL,添加调用者的读写权限
            FileSecurity fileAcl = new FileSecurity();
            fileAcl.AddAccessRule(new FileSystemAccessRule(
                callerSid,
                FileSystemRights.Read | FileSystemRights.Write,
                InheritanceFlags.None,
                PropagationFlags.None,
                AccessControlType.Allow));

            // 将ACL应用到文件
            File.SetAccessControl(filePath, fileAcl);
        }

        return filePath;
    }
    finally
    {
        // 释放所有资源,避免泄漏
        if (callerToken != IntPtr.Zero)
            CloseHandle(callerToken);
        if (serverSecurity != null)
            Marshal.ReleaseComObject(serverSecurity);
        if (pServerSecurity != IntPtr.Zero)
            Marshal.Release(pServerSecurity);
    }
}

关键注意事项

  • 资源释放:所有获取的COM接口指针、系统令牌必须在finally块中释放,防止资源泄漏。
  • 模拟上下文:必须在ImpersonateClient后立即获取令牌,并在完成后调用RevertToSelf恢复身份,避免后续代码以调用者身份执行。
  • 权限兼容性:如果调用者的COM模拟级别为Anonymous,OpenThreadToken可能失败,此时降级获取进程令牌(适用于进程内COM调用场景)。
  • ACL继承:若文件所在目录存在权限继承规则,需调整InheritanceFlags和PropagationFlags参数,确保添加的权限优先生效。

内容的提问来源于stack exchange,提问作者jmucchiello

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:25:54