다음을 통해 공유


Windows에서 앱 작업에 대한 호출자 검색 및 필터링

이 문서에서는 Windows의 App Actions용 공급자 앱이 작업을 쿼리하거나 호출할 수 있는 앱 집합을 제한하기 위해 구현할 수 있는 몇 가지 메커니즘을 설명합니다. 구독 기반 LLM 서비스와 같이 사용당 비용이 있는 리소스를 사용하는 작업과 같은 시나리오를 지원하도록 설계된 선택적 구현 단계입니다.

작업 정의 JSON 파일에서 allowedAppInvokers 설정

시스템에 작업을 등록하는 데 사용되는 작업 정의 JSON 파일에는 각 작업 정의에 대해 allowedAppInvokers 필드가 포함됩니다. 이 필드의 값은 GetActionsForInputs 또는 GetAllActions 호출을 통해 작업을 검색할 수 있는 AUMID(애플리케이션 사용자 모델 ID) 목록 입니다. 와일드카드가 지원됩니다. "*"는 모든 AppUserModelID와 일치합니다. allowedAppInvokers가 생략되었거나 빈 목록인 경우 어떤 앱도 작업을 검색할 수 없습니다. AppUserModelID에 대한 자세한 내용은 애플리케이션 사용자 모델 ID를 참조하세요. 작업 정의 JSON 파일에 대한 자세한 내용은 Windows의 앱 작업에 대한 작업 정의 JSON 스키마를 참조하세요.

allowedAppInvokers 필드는 앱이 작업을 쿼리할 수 있는 것만 제한합니다. 작업 공급자가 시스템에 등록되는 방식 때문에 allowedAppInvokers에 의한 쿼리가 제한되더라도 앱에서 작업을 호출할 수 있습니다. 이 문서의 나머지 부분은 런타임에 작업에 대한 호출자를 제한하는 다양한 방법에 대해 설명합니다.

URI 시작 활성화를 사용할 때 호출자 감지

다음 섹션에서는 URI 시작 활성화를 사용하는 작업 공급자 앱에 대해 런타임에 작업의 호출자를 검색하는 다양한 방법을 설명합니다.

호출자 패키지 패밀리 이름 가져오기 (Rich activation에만 해당)

최신의 풍부한 활성화를 사용하는 공급자 앱은 시작 시 공급자 앱에 전달된 ProtocolForResultsActivatedEventArgs 클래스의 CallerPackageFamilyName 속성 값을 확인할 수 있습니다. 값이 "_cw5n1h2txyewy" 문자열로 끝나는 경우 Windows에서 작업을 호출했습니다. 다양한 활성화에 대한 자세한 내용은 앱 수명 주기 API를 사용한 리치 활성화를 참조하세요.

const string windowsPFN = "_cw5n1h2txyewy";
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();

    if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
    {

        ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;

        if (protocolForResultsArgs.CallerPackageFamilyName.EndsWith(windowsPFN))
        {
            // Perform the action if invoked by Windows
        }
        
    }

    _window = new MainWindow();
    _window.Activate();
}

$.Token URI 쿼리 문자열 매개 변수를 사용합니다.

URI 활성화를 사용하는 작업 공급자는 작업 정의 JSON 매니페스트 파일에서 해당 작업을 시작하는 URI를 선언합니다. 앱이 호출될 때 실행 URI의 쿼리 문자열에 고유 식별자 토큰을 전달하도록 Action Runtime에 지시하는 특수 쿼리 문자열 매개 변수 $.Token 가 있습니다. 다음 예제에서는 매개 변수를 사용하여 URI $.Token 를 선언하는 구문을 보여줍니다.

"invocation": {
  "type": "Uri",
  "uri": "urilaunchaction-protocol://messageaction?token=${$.Token}",
  "inputData": {
    "message": "${message.Text}"
  }
}

작업이 시작되면 활성화 URI에서 쿼리 문자열 매개 변수 Token 를 검색합니다. GetActionInvocationContextFromToken 호출에 이 토큰을 전달합니다. 작업 런타임은 작업 공급자를 시작할 때 제공한 토큰에 대해 토큰의 유효성을 검사합니다. 값이 일치하면 유효한 ActionInvocationContext 개체가 반환되어 작업 런타임에서 작업이 호출되었는지 확인합니다.

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();

    if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
    {

        ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;

        using (ActionRuntime runtime = ActionRuntimeFactory.CreateActionRuntime())
        {

            var launchUri = protocolForResultsArgs.Uri;
            var query = HttpUtility.ParseQueryString(launchUri.Query);
            var token = query["Token"];
            if(token != null)
            {
                var context = runtime.GetActionInvocationContextFromToken(token);
                if(context == null)
                {
                    // Caller is not Action Runtime
                    return;
                }
            }

이 예제에서는 Microsoft.AI.Actions NuGet 패키지에서 제공하는 도우미 메서드를 사용합니다. 작업 공급자에서 이 패키지를 참조하는 방법에 대한 자세한 내용은 Windows에서 앱 작업에 대한 URI 시작 구현을 참조하세요.

COM 활성화에서 실행 시간 동안 호출자 감지

다음 섹션에서는 런타임에 작업을 호출한 앱의 AUMID를 검색하는 프로세스를 안내합니다. 이 섹션의 예제에서는 작업 런타임의 AUMID를 확인합니다. 앱은 다른 앱의 AUMID를 필터링하거나 PFN(패키지 패밀리 이름)을 사용하여 특정 패키지에 포함된 모든 앱을 필터링하도록 선택할 수 있습니다.

CsWin32 Nuget 패키지에 대한 참조 추가

이 섹션에 표시된 예제 코드는 Win32 API를 사용하여 호출자의 패키지 패밀리 이름을 가져옵니다. C# 앱에서 Win32 API를 보다 쉽게 호출하기 위해 이 예제에서는 빌드 시 Win32 API에 대한 C# 래퍼를 생성하는 CsWin32 NuGet 패키지를 사용합니다. 자세한 내용은 CsWin32 github 리포지토리를 참조하세요.

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다.
  2. 찾아보기 탭에서 "Microsoft.Windows.CsWin32"를 검색합니다.
  3. 이 패키지를 선택하고 모든 프롬프트에 따라 설치를 클릭합니다.

이 패키지를 설치하면 C# 래퍼가 생성될 Win32 API를 지정하는 방법을 자세히 설명하는 추가 정보가 표시됩니다. 이렇게 하려면 사용할 메서드를 나열하는 텍스트 파일을 만들어야 합니다.

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 새 항목 추가...를> 선택합니다.
  2. TextFile 템플릿을 선택합니다.
  3. 파일 이름을 "NativeMethods.txt"로 지정하고 추가를 클릭합니다.
  4. NativeMethods.txt 두 번 클릭하여 파일을 열고 다음 줄을 추가합니다.
CoRegisterClassObject
CoRevokeClassObject
CoImpersonateClient
OpenThreadToken
GetCurrentThread
CoRevertToSelf
GetPackageFamilyNameFromToken
GetLastError

ActionProvider 클래스를 업데이트하여 작업 호출자 PFN 확인

다음 도우미 메서드는 NativeMethods.txt 지정된 Win32 API를 사용하여 호출 프로세스에 대한 AUMID를 검색하고 전달된 AUMID와 일치하는 경우 true를 반환합니다. 이 코드의 작동 방식에 대한 자세한 설명은 '클라이언트 가장하기'를 참조하십시오.

static bool WasInvokedByAUMID(string aumid)
{
    Windows.Win32.Foundation.HRESULT hr = PInvoke.CoImpersonateClient();

    var RPC_E_CALL_COMPLETE = unchecked((int)0x80010117);

    if (hr.Value == RPC_E_CALL_COMPLETE)
    {
        return false;
    }

    Microsoft.Win32.SafeHandles.SafeFileHandle hThreadTok;


    bool bRes = PInvoke.OpenThreadToken(
        new Microsoft.Win32.SafeHandles.SafeFileHandle(PInvoke.GetCurrentThread(), true),
        Windows.Win32.Security.TOKEN_ACCESS_MASK.TOKEN_QUERY,
        true,
        out hThreadTok
    );

    if (bRes == false)
    {
        var e = Marshal.GetLastWin32Error();
        Console.WriteLine("Unable to read thread token. " + e);
        hThreadTok.Close();
        return false;
    }


    PInvoke.CoRevertToSelf();


    var invokerAumid = new Span<char>();
    uint aumidLength = 0;

    PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);
    invokerAumid = new char[aumidLength].AsSpan();
    PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);

    hThreadTok.Close();

    if (invokerAumid.Trim('\0').EndsWith(aumid))
    {
        return true;
    }
    else
    {
        return false;
    }
}

CoImpersonateClient가 구현되는 방식 때문에 앱의 시작 프로시저가 아니라 작업 호출 내에서 도우미 메서드를 호출하는 것이 중요합니다.

다음 예제에서는 Microsoft.AI.Actions 코드 생성 프레임워크를 사용하여 구현된 작업에서 Action Runtime AUMID에 대한 검사를 보여 줍니다.

const string actionRuntimeAUMID = "_cw5n1h2txyewy!ActionRuntime";
[WindowsActionInputCombination(
    Inputs = ["Contact", "Message"],
    Description = "Send '${Message.Text}' to '${Contact.Text}'"
)]
public async Task<SendMessageResult> SendMessage(
    [Entity(Name = "Contact")] string contact,
    [Entity(Name = "Message")] string? message,
    InvocationContext context)
{
    if (!WasInvokedByAUMID(actionRuntimeAUMID))
    {
        context.Result = ActionInvocationResult.Unavailable;
        return new SendMessageResult
        {
            Text = context.EntityFactory.CreateTextEntity("")
        };
    }
    
    // Your action logic here
    string result = await ProcessMessageAsync(contact, message);

    return new SendMessageResult
    {
        Text = context.EntityFactory.CreateTextEntity(result)
    };
}