即使管理员已经同意,也会触发 ADAL 用户同意

Posted

技术标签:

【中文标题】即使管理员已经同意,也会触发 ADAL 用户同意【英文标题】:ADAL user consent triggered even when admin has already consented 【发布时间】:2015-06-04 08:16:47 【问题描述】:

我创建了一个使用 Azure Active Directory 进行身份验证的 Web API。它使用多租户 AAD。为了测试它,我还创建了一个控制台应用程序,它使用 ADAL 库对 AAD 进行身份验证,以便我可以访问我的 API。在主要的 AAD 租户中,一切都运行良好,因为我不需要授予任何东西。但是当从第二个租户访问应用程序时,我首先触发管理员同意流程(添加prompt=admin_consent)。但是当我退出并再次打开应用程序时,如果我尝试使用在 AAD 上没有管理员权限的用户登录,它会尝试打开用户同意但失败(因为用户无权允许访问AAD)。如果我已经给予管理员同意,用户不应该已经同意吗?

测试应用的代码是:

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; 
    

【问题讨论】:

【参考方案1】:

您的测试应用是本机客户端。在 OAuth 术语中,它是一个公共客户端。这些条款适用于没有自己的客户端密码或证书凭证的任何客户端。管理员同意功能不适用于本机客户端,仅适用于 Web 应用程序。理想情况下,当本机应用程序尝试获得管理员同意时,会返回一个错误,表明该组合不受支持。我们将考虑在未来返回此类错误以防止此类混淆。

与此同时,无法阻止用户在登录本地客户端时看到同意对话框。

如果本机应用程序调用一个 Web API,而本机应用程序和 Web API 都属于同一个供应商/租户,情况会稍微复杂一些。如果设置正确,那么用户将看到一个组合同意对话框,允许用户同意本机应用程序和 web api。对 web api 的同意将被永久记录。对本机应用程序的同意将仅适用于该登录会话,其方式与不涉及 Web api 的方式相同。如果以这种方式涉及 Web api,则可以调用管理员同意。然后管理员可以代表所有用户同意 Web api。但是,个人用户仍需要同意本机应用程序。

要正确设置此同意链,您需要在 Web api 的应用程序清单中使用“knownClientApplication”属性。您将此属性的值设置为本机应用程序的客户端 ID。您可以在此示例中看到这一点:

https://github.com/AzureADSamples/NativeClient-WebAPI-MultiTenant-WindowsStore/blob/master/README.md

基本上是通过门户下载应用程序清单,更新此特定值,然后上传。

这里有一些关于这些主题的更全面的文档:

https://msdn.microsoft.com/en-us/library/azure/dn132599.aspx

更新: 上面对调用 web api 的本机应用程序的解释中的一项规定是它们必须在同一个租户中。如果他们不在同一个租户中,那么事情会变得更加复杂。当 ISV 创建了他们希望提供给客户编写的应用程序的 Web API 时,就会出现这种情况。为了让应用程序获取资源的令牌,两个应用程序必须在同一个租户中注册。因此,客户需要做的第一件事就是在他们自己的租户中注册 web api。如果 web api 在应用程序库中,那么他们只需去那里安装应用程序。 ISV 不必将他们的应用程序放在应用程序库中以允许客户注册它,但注册变得更加复杂。 ISV 需要创建一个网站,在 ISV 租户中注册,客户管理员可以访问该网站。该网站需要登录管理员才能以触发同意过程的方式获取 Web api 的令牌。完成后,API 将在客户租户中注册并可供客户应用使用。

要让您的应用进入应用库,请按照本页底部附近的说明进行操作:

http://azure.microsoft.com/en-us/marketplace/active-directory/

【讨论】:

好的。然后我必须假设管理员没有向他们的用户授予权限USERS MAY GIVE APPLICATIONS PERMISSION TO ACCESS THEIR DATA 的场景不支持本机客户端-> Webapi?还是有其他方法,比如手动将应用添加到其他租户的 AAD? 我已经更新了上面的答案。如果这不能回答您的问题,请告诉我。 这就是我设置它的方式(我认为)。另外一个问题:如果我们仍然需要同意申请,其他租户的管理员是否也需要允许USERS MAY GIVE APPLICATIONS PERMISSION TO ACCESS THEIR DATA 权限?还是我错过了什么?谢谢! 你问的是web api还是native app?他们每个人都有自己的权限。无论哪种方式,用户自己都可以在没有管理员的情况下同意。管理员可以代表所有用户同意 web api,这将阻止用户看到 web api 的同意。如果原生应用需要直接访问图形或其他用户资源,并且管理员已同意 Web api 权限,则用户每次登录时都会看到一个只有原生应用权限的同意对话框。 我说的是原生应用。我们的场景是我们有一个门户,我们希望允许用户使用他们自己的 Office 365 帐户或另一个 azure 广告帐户(来自他们自己的租户)登录它,这很好。除此之外,我们还想为这个门户提供一个 API,这样他们就可以从本地与之交互。网络应用程序(winforms)。但是,由于我们不控制其他租户,我不知道我们是否做得正确。

以上是关于即使管理员已经同意,也会触发 ADAL 用户同意的主要内容,如果未能解决你的问题,请参考以下文章

即使似乎授予访问权限,Google Calendar API在OAuth期间也会出现403错误

是否可以从 iphone 静默发送位置数据(当然需要用户同意)?

AADSTS65001:用户或管理员未同意使用 ID 为“<应用程序 ID>”的应用程序

OAuth 同意屏幕的待定开发人员操作

如何更改 Google 同意屏幕电子邮件?

你不小心已“同意” 许多互联网“霸王条款”