问题描述
我创建了一个Web API,该API使用Azure Active Directory进行身份验证。它使用多租户AAD。为了测试它,我还创建了一个控制台应用程序,该应用程序使用ADAL库针对AAD进行身份验证,以便可以访问我的API。在AAD的主要租户中,所有人都运转良好,因为我不需要授予任何东西。但是,当从第二个租户访问该应用程序时,我首先触发了管理员同意流程(添加 prompt = admin_consent
)。但是,当我退出并再次打开该应用程序时,如果我尝试使用在AAD上没有管理员权限的用户登录,它会尝试打开用户同意,但会失败(因为用户无权允许访问AAD)。如果我已经获得管理员的同意,那么是否也应该征得用户的同意?
I've created a Web API which uses Azure Active Directory for its authentication. It uses a multi-tenant AAD. To test it, I also created a console app which uses the ADAL library to authenticate against AAD so I can access my API. In the main AAD tenant all is working well, because I don't need to grant anything. But when accessing the app from a second tenant, I first trigger the admin consent flow (adding a prompt=admin_consent
). But when I exit and open the app again, if I try to login with a user with no admin rights on the AAD, it tries to open the user consent and it fails (because the users don't have right to allow access to the AAD). If I already given admin consent, shouldn't the users already be consented?
测试应用的代码为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Authentication;
using System.Threading.Tasks;
using System.Web;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
namespace TestConsole
{
internal class Program
{
private const string _commonAuthority = "https://login.microsoftonline.com/common/";
private static void Main(string[] args)
{
ConsoleKeyInfo kinfo = Console.ReadKey(true);
AuthenticationContext ac = new AuthenticationContext(_commonAuthority);
while (kinfo.Key != ConsoleKey.Escape)
{
if (kinfo.Key == ConsoleKey.A)
{
AuthenticationResult ar = ac.AcquireToken("https://babtecportal.onmicrosoft.com/Portal2015.Api", "client_id", new Uri("https://out.es"), PromptBehavior.Auto, UserIdentifier.AnyUser, "prompt=admin_consent");
}
else if (kinfo.Key == ConsoleKey.C)
{
Console.WriteLine("Token cache length: {0}.", ac.TokenCache.Count);
}
else if (kinfo.Key == ConsoleKey.L)
{
ac.TokenCache.Clear();
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, _commonAuthority + "oauth2/logout?post_logout_redirect_uri=" + HttpUtility.UrlEncode("https://out.es"));
var response=client.SendAsync(request).Result;
Console.WriteLine(response.StatusCode);
ac=new AuthenticationContext(_commonAuthority);
}
else
{
int num;
if (int.TryParse(Console.ReadLine(), out num))
{
try
{
AuthenticationResult ar = ac.AcquireToken("https://babtecportal.onmicrosoft.com/Portal2015.Api", "client_id", new Uri("http://out.es"),PromptBehavior.Auto,UserIdentifier.AnyUser);
ac = new AuthenticationContext(ac.TokenCache.ReadItems().First().Authority);
// Call Web API
string authHeader = ar.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("http://localhost:62607/api/Values?num={0}", num));
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
string responseString = response.Content.ReadAsStringAsync().Result;
Values vals = JsonConvert.DeserializeObject<Values>(responseString);
Console.WriteLine("Username: {0}", vals.Username);
Console.WriteLine("Name: {0}", vals.FullName);
vals.Range.ToList().ForEach(Console.WriteLine);
}
else
{
Console.WriteLine("Status code: {0}", response.StatusCode);
Console.WriteLine("Reason: {0}", response.ReasonPhrase);
}
}
catch (AdalException ex)
{
Console.WriteLine(ex.Message);
}
}
}
kinfo = Console.ReadKey(true);
}
}
}
public class Values
{
public string Username { get; set; }
public string FullName { get; set; }
public IEnumerable<int> Range { get; set; }
}
}
推荐答案
您的测试应用是本地客户端。用OAuth术语来说,它是一个公共客户端。这些条款适用于没有客户端密码或证书凭据的任何客户端。管理员同意功能不适用于本机客户端,仅适用于Web应用程序。理想情况下,当尝试对本机应用程序征得管理员同意时,将返回错误,表明该组合不受支持。我们将考虑在将来返回这样的错误,以防止这种混淆。
Your test app is a native client. In OAuth terms it is a public client. Those terms apply to any client that does not have a client secret or certificate credential of its own. The admin consent feature does not apply to native clients and only works for web applications. Ideally, there would be an error returned when admin consent is attempted for a native app that would indicate that the combination is not supported. We are going to look in to returning such an error in the future to prevent this kind of confusion.
同时,无法阻止用户看到
In the meantime, there is no way to prevent users from seeing the consent dialogue when they sign in to a native client.
如果本地应用程序正在调用同时包含本地应用程序和Web api的Web api,则情况会更加复杂。由同一供应商/租户拥有。如果正确设置,则用户将看到一个组合的同意对话框,该对话框允许用户同意本机应用程序和Web api。对网络api的同意将被永久记录。对本机应用程序的同意将仅以与不涉及Web api涉及的方式相同的方式应用于该登录会话。如果以这种方式涉及Web api,则可以调用管理员同意。然后,管理员可以代表所有用户同意Web api。但是,单个用户仍然需要同意本机应用程序。
The situation is somewhat more complicated if the native app is calling a web api where both the native app and web api are owned by the same vendor/tenant. If this is set up correctly then the user will see a combined consent dialog that allows the user to consent to both the native app as well as the web api. The consent to the web api will be recorded permanently. The consent to the native app will only apply to that sign in session in the same way it would if no web api were involved. If a web api is involved in this way then admin consent can be invoked. The admin can then consent to the web api on behalf of all users. However, individual users will still need to consent to the native app.
要正确设置此同意链,您需要在以下应用程序清单中使用 knownClientApplication属性网络API。您将此属性的值设置为本机应用程序的客户端ID。您可以在此示例中看到这样做:
To correctly set up this consent chain you need to use the 'knownClientApplication' attribute in the application manifest of the web api. You set the value of this attribute to the client id of the native app. You can see this being done in this sample:
基本上,您是通过门户下载应用程序清单的,更新此特定值,然后将其上传。
Essentially you download the application manifest through the portal, update this particular value, and then upload it.
关于这些主题,有一些更全面的文档此处:
There is some more comprehensive documentation on these topics here:
更新:
上面对本地应用程序调用Web api的解释中的规定之一是,它们都必须位于同一租户中。如果他们不在同一租户中,那么事情会变得更加复杂。如果ISV创建了一个Web API,他们希望将其提供给客户编写的应用程序,就属于这种情况。为了使一个应用程序获得资源的令牌,两个应用程序都必须在同一租户中注册。因此,客户要做的第一件事就是在自己的租户中注册Web api。如果网络api位于应用程序库中,则他们只需去那里安装应用程序即可。 ISV不必将其应用程序放在应用程序库中就可以让客户进行注册,但是注册变得更加复杂。 ISV将需要创建一个在ISV租户中注册的网站,客户管理员可以访问该网站。该网站需要登录管理员才能以某种方式触发Web api的令牌。完成此操作后,该api将在客户租户中注册并可供客户应用使用。
Update:One of the stipulations in the above explanation of a native app calling a web api was that they both had to be in the same tenant. If they are not in the same tenant then things get more complicated. This is the case when an ISV has created a web API that they want to make available to apps written by customers. In order for an app to get a token for a resource both apps must be registered in the same tenant. Thus, the first thing the customer will need to do is get the web api registered in their own tenant. If the web api is in the app gallery then they simply go there and install the app. The ISV does not have to have their app in the app gallery to allow customers to register it, but registration gets more complicated. The ISV will need to create a web site, registered in the ISV tenant, that the customer admin can visit. That website needs sign in the admin to get a token for the web api in a way that will trigger the consent process. Once that is complete, then the api will be registered in the customer tenant and available to customer apps.
要将您的应用加入应用程序库,请按照此页面底部:
To get your app in to the app gallery follow the instructions near the bottom of this page:
这篇关于即使管理员已经同意,也会触发ADAL用户同意的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!