GOAL:我想要一个可以通过Windows桌面应用程序调用的Azure函数(HttpTrigger)。我希望对功能的访问由Active Directory控制,只有经过授权的用户才能调用它。
当前状态:我按照指南here创建具有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 will be created with TLS 1.2 by default。但是,WPF应用程序默认使用TLS 1.0。因此,我们无法调用Azure函数。关于如何修复,请参考document
关于如何调用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);
}
}
}