azure - 从 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 函数吗?如何?
解决方案
关于这个问题,它可能与 TLS 版本有关。据我所知,目前, Azure App Service 将默认使用 TLS 1.2 创建。但是,WPF 应用程序默认使用 TLS 1.0。所以我们不能调用 Azure 函数。关于如何修复它,请参阅文档
更新
关于如何调用 Azure AD 投射的 Azure 函数,请参考以下步骤
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);
}
}
}
推荐阅读
- wordpress - Woocommerce 客户登录问题
- c++ - '\t' 转义序列与手动四个空格的转义序列有何不同?
- opencv - Canny之后如何避免潜在的噪音?
- typescript - Nestjs GraphQL Apollo Federation - 解析器在调试中有一个对象,返回一个带有空值的对象
- javascript - 检测到提及/用户 ID 参数时出错 (discord.js)
- ios - GCDWebServer:使用真 iPhone 时无法打开主页
- reactjs - useFormik React - 更新初始值
- tensorflow - Tensorflow:批量分类图像
- google-bigquery - 如何在 BigQuery 中手动强制报告运行?
- cakephp - 如何在 CakePHP 中根据 2 列条件获取数据