首页 > 解决方案 > 从 Windows 桌面应用程序调用具有 AD 授权的 Azure 函数

问题描述

目标: 我想要一个可以通过 Windows 桌面应用程序调用的 Azure Functions (HttpTrigger)。我希望对功能的访问由 Active Directory 控制,并且只有授权用户才能调用它。

当前状态: 我按照此处的指南创建了一个具有 AD 授权的桌面应用程序。我还创建了一个 Azure 函数,我在其中添加了一个带有“使用 Azure Active Directory 登录”的“应用程序服务身份验证”,并创建了一个新的应用程序注册来处理这个问题。在我的桌面应用程序中,我添加了一个调用此函数的按钮。

问题: 当我通过浏览器中的链接直接调用该函数时,一切正常;如果我被授权,它会调用该函数,如果我不是,我将被重定向到登录屏幕,并在成功登录后(仅限授权用户)我得到该函数的结果。当我尝试通过我的桌面应用程序执行此操作时,问题就来了。当我按下函数调用按钮时,我被重定向到登录屏幕,一旦我使用我的凭据成功登录,我就会收到错误消息:

AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: <app-id>

当在我的应用程序注册中我没有“移动和桌面应用程序”的身份验证选项时,会发生这种情况,只有“Web”。如果我添加“移动和桌面应用程序”选项,那么原始按钮(来自上面的教程)可以登录并正常工作(在前一种情况下,它给了我同样的错误)但是这一次,当我尝试通过我添加的按钮调用该函数,程序因错误而崩溃:

Inner Exception 1:
HttpRequestException: An error occurred while sending the request.

Inner Exception 2:
WebException: The underlying connection was closed: An unexpected error occurred on a send.

Inner Exception 3:
IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.

Inner Exception 4:
SocketException: An existing connection was forcibly closed by the remote host

如果我强制使用 TLS 1.2,我会收到 401 错误:“您无权查看此目录或页面。”。如果我尝试调用一个不使用AD授权的函数,那么整个过程是成功的。我的代码:

        private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
        {
            AuthenticationResult authResult = null;
            var app = App.PublicClientApp;
            ResultText.Text = string.Empty;
            TokenInfoText.Text = string.Empty;

            var accounts = await app.GetAccountsAsync();
            var firstAccount = accounts.FirstOrDefault();

            try
            {
                authResult = await app.AcquireTokenSilent(scopes, firstAccount)
                    .ExecuteAsync();
            }
            catch (MsalUiRequiredException ex)
            {
                System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

                try
                {
                    authResult = await app.AcquireTokenInteractive(scopes)
                        .WithAccount(accounts.FirstOrDefault())
                        .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
                        .WithPrompt(Prompt.SelectAccount)
                        .ExecuteAsync();
                }
                catch (MsalException msalex)
                {
                    ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
                }
            }
            catch (Exception ex)
            {
                ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
                return;
            }

            if (authResult != null)
            {
                this.SignOutButton.Visibility = Visibility.Visible;
                string token = authResult.AccessToken;

                using (var client = new HttpClient())
                {
                    // With an explicit selection of the security protocol the program does not crash.
                    // Instead it gives 401 Unauthorized error, when already signed in.
                    // Without the following line, the program crashes.
                    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


                    string requestUrl = $"the_URL_of_my_function";

                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
                    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

                    HttpResponseMessage response = client.SendAsync(request).Result;
                    var responseString = response.Content.ReadAsStringAsync().Result;
                    ResultText.Text = responseString;
                    DisplayBasicTokenInfo(authResult);
                }
            }
        }

问题: 我可以通过 Windows 桌面应用程序调用/使用需要授权的 Azure 函数吗?如何?

标签: azureazure-active-directoryauthorizationazure-functionsdesktop-application

解决方案


关于这个问题,它可能与 TLS 版本有关。据我所知,目前, Azure App Service 将默认使用 TLS 1.2 创建。但是,WPF 应用程序默认使用 TLS 1.0。所以我们不能调用 Azure 函数。关于如何修复它,请参阅文档


更新

关于如何调用 Azure AD 投射的 Azure 函数,请参考以下步骤

  1. 为 Azure Function 配置 Azure AD 在此处输入图像描述

  2. 在 Azure AD 中创建客户端应用程序

  3. 配置API权限并获取我们需要的范围 在此处输入图像描述 在此处输入图像描述

  4. 代码

string[] scopes = new string[] 
{"https://testfun08.azurewebsites.net/user_impersonation" };// the scope you copy
 
 private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
        {
      // get token
          AuthenticationResult authResult = null;
            var app = App.PublicClientApp;
            ResultText.Text = string.Empty;
            TokenInfoText.Text = string.Empty;

            var accounts = await app.GetAccountsAsync();
            var firstAccount = accounts.FirstOrDefault();

            try
            {
                authResult = await app.AcquireTokenSilent(scopes, firstAccount)
                    .ExecuteAsync();
            }
            catch (MsalUiRequiredException ex)
            {
                System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

                try
                {
                    authResult = await app.AcquireTokenInteractive(scopes)
                        .WithAccount(accounts.FirstOrDefault())
                        .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
                        .WithPrompt(Prompt.SelectAccount)
                        .ExecuteAsync();
                }
                catch (MsalException msalex)
                {
                    ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
                }
            }
            catch (Exception ex)
            {
                ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
                return;
            }

       //call Azure function
            if (authResult != null)
            {
                this.SignOutButton.Visibility = Visibility.Visible;
                string token = authResult.AccessToken;

                using (var client = new HttpClient())
                {
                    // Without the following line, the program crashes.
                    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


                    string requestUrl = $"the_URL_of_my_function";
                 client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);


                    HttpResponseMessage response = client.SendAsync(request).Result;
                    var responseString = response.Content.ReadAsStringAsync().Result;
                    ResultText.Text = responseString;
                    DisplayBasicTokenInfo(authResult);
                }
            }
        }

在此处输入图像描述


推荐阅读