多租户 Azure 移动应用程序服务调用因 401.71 而失败

Multi-tenant Azure Mobile App service calls failing with 401.71

作为序言,我是 Azure 编程和 Azure AD 身份验证的新手,我一直在按照我在各种站点(包括 MS)上找到的教程来学习这一步。我正在使用 Xcode v7.2、iOS v1.2.4 的 ADAL、Visual Studio 2015 Update 1 和 Azure App Service Tools v2.8.1。

我有一个现有的本机 iOS 应用程序,我需要能够通过它对多个 Azure Active Directory 实例用户进行身份验证。这些用户是内部用户和外部用户(注册我们服务的客户)。为此,我实验性地实现了以下高级架构:

Native Client App (iOS / obj-c) -> ADAL iOS library -> (Azure AD authentication) -> Azure Mobile App (service layer)

iOS 应用程序利用 ADAL iOS 库获取访问令牌,用于调用 Azure 移动应用程序项目中的授权 Web API 服务。

我能够对来自两个租户(内部 Azure AD 和外部 Azure AD)的用户进行身份验证,但只有与服务(内部)在同一租户中的用户才能调用经过身份验证的 APIs。我从外部租户使用的测试用户帐户被设置为全局管理员,并且在进行身份验证时,我在本机应用程序中看到了适当的同意视图。然后我可以点击同意并收到访问令牌。但是,当使用该令牌调用测试 API 时,我得到了 401 返回。服务器上 Azure 移动应用程序的详细日志显示以下消息(下面的所有 URL 都是 https,我只是没有代表 post 它们这样):

2016-01-12T13:00:55  PID[7972] Verbose     Received request: GET MyAzureMobileApp.azurewebsites.net/api/values
2016-01-12T13:00:55  PID[7972] Verbose     Downloading OpenID configuration from sts.windows.net/<internal AD GUID>/.well-known/openid-configuration
2016-01-12T13:00:55  PID[7972] Verbose     Downloading OpenID issuer keys from login.windows.net/common/discovery/keys
2016-01-12T13:00:56  PID[7972] Warning     JWT validation failed: IDX10205: Issuer validation failed. Issuer: 'sts.windows.net/<external AD GUID>/'. Did not match: validationParameters.ValidIssuer: 'sts.windows.net/<internal ad guid>/' or validationParameters.ValidIssuers: 'null'..
2016-01-12T13:00:56  PID[7972] Information Sending response: 401.71 Unauthorized

我在几个 post 中读到,您可以通过在 TokenValidationParameters 中设置 ValidateIssuer 参数来禁用服务中的令牌颁发者验证 为假。我试过这样做,但似乎没有任何效果。这是我的 Azure 移动应用程序项目中的代码:

启动代码:

// Startup.cs
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MyAzureMobileApp.Startup))]
namespace MyAzureMobileApp
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureMobileApp(app);
            ConfigureAuth(app);
        }
    }
}

MobileApp 的代码 -- 这应该是 stock,由 Azure Mobile App 项目模板生成:

// Startup.MobileApp.cs  
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Web.Http;
using Microsoft.Azure.Mobile.Server;
using Microsoft.Azure.Mobile.Server.Authentication;
using Microsoft.Azure.Mobile.Server.Config;
using MyAzureMobileApp.DataObjects;
using MyAzureMobileApp.Models;
using Owin;

namespace MyAzureMobileApp
{
    public partial class Startup
    {
        public static void ConfigureMobileApp(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            new MobileAppConfiguration()
                .UseDefaultConfiguration()
                .ApplyTo(config);

            // Use Entity Framework Code First to create database tables based on your DbContext
            Database.SetInitializer(new MobileServiceInitializer());

            MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();

            if (string.IsNullOrEmpty(settings.HostName))
            {
                app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
                {
                    // This middleware is intended to be used locally for debugging. By default, HostName will
                    // only have a value when running in an App Service application.
                    SigningKey = ConfigurationManager.AppSettings["SigningKey"],
                    ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
                    ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
                    TokenHandler = config.GetAppServiceTokenHandler()
                });
            }

            app.UseWebApi(config);
        }
    }

    public class MobileServiceInitializer : CreateDatabaseIfNotExists<MobileServiceContext>
    {
        protected override void Seed(MobileServiceContext context)
        {
            List<TodoItem> todoItems = new List<TodoItem>
            {
                new TodoItem { Id = Guid.NewGuid().ToString(), Text = "First item", Complete = false },
                new TodoItem { Id = Guid.NewGuid().ToString(), Text = "Second item", Complete = false }
            };

            foreach (TodoItem todoItem in todoItems)
            {
                context.Set<TodoItem>().Add(todoItem);
            }

            base.Seed(context);
        }
    }
}

认证启动码:

// Startup.Auth.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Linq;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.ActiveDirectory;
using Owin;

namespace MyAzureMobileApp
{
    public partial class Startup
    {
        // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {            
            app.UseWindowsAzureActiveDirectoryBearerAuthentication(
                new WindowsAzureActiveDirectoryBearerAuthenticationOptions
                {                    
                    Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
                    AuthenticationMode = AuthenticationMode.Active,
                    TokenValidationParameters = new TokenValidationParameters()
                    {          
                        ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
                        ValidateIssuer = false
                    }
                });

        }
    }
}

服务实现:

using System.Web.Http;
using Microsoft.Azure.Mobile.Server.Config;

namespace MyAzureMobileApp.Controllers
{
    // Use the MobileAppController attribute for each ApiController you want to use  
    // from your mobile clients 
    [MobileAppController]
    // Use the MobileAppController attribute for each ApiController you want to use  
    // from your mobile clients 
    [Authorize]
    public class ValuesController : ApiController
    {
        // GET api/values
        public string Get()
        {
            return "GET returned: Hello World!";
        }

        // POST api/values
        public string Post()
        {
            return "POST returned: Hello World!";
        }
    }
}

还有我在 web.config 中的 appSettings 部分:

  <appSettings>
    <add key="PreserveLoginUrl" value="true" />
    <!-- Use these settings for local development. After publishing to your
    Mobile App, these settings will be overridden by the values specified
    in the portal. -->
    <add key="MS_SigningKey" value="Overridden by portal settings" />
    <add key="EMA_RuntimeUrl" value="Overridden by portal settings" />
    <!-- When using this setting, be sure to add matching Notification Hubs connection
    string in the connectionStrings section with the name "MS_NotificationHubConnectionString". -->
    <add key="MS_NotificationHubName" value="Overridden by portal settings" />
    <add key="ida:ClientId" value="-- MyAzureMobileApp App ID from Azure AD --" />
    <add key="ida:Tenant" value="InternalTestAD.onmicrosoft.com" />
    <add key="ida:Audience" value="https://InternalTestAD.onmicrosoft.com/MyAzureMobileApp" />
    <add key="ida:Password" value="-- password value removed --" />        
  </appSettings>

除了 TokenValidationParameters 集合 WindowsAzureActiveDirectoryBearerAuthenticationOptions[=52= 中的 属性,我没有看到指定有效令牌颁发者的地方].

根据我对代码的理解,我应该禁用发行者验证,但我尝试在此处添加外部 Azure AD STS URL。不幸的是,它似乎没有任何效果。

有人知道这段代码是否由于某种原因被忽略或覆盖了吗?我是否遗漏了一些其他设置来完全禁用颁发者验证或指定有效颁发者列表?

我当然可以根据要求提供更多信息,我只是不确定还有什么可能是相关的。

谢谢!

我相信我已经找到我的验证逻辑被忽略的原因。在 Azure App Services 中我的 Web api 站点的设置中,我通过在 "Authentication/Authorization" > "Azure Active Directory Settings" 刀。事实证明,当您将拥有多个发行者时(如在我的多租户场景中)您应该将此字段留空 .

JWT 将针对您在该文本框中提供的颁发者进行验证,这是完全合理的。对我来说不太直观的是,当你有不止一个发行人时,你应该把它留空。也许 MS 可以将其添加到其上方的信息气泡中?或者提供一些允许多个发行人 URLs.

的机制

希望这可以为其他人节省一些时间来解决这个问题。

只是想指出,如果您配置了身份验证,并且设置了主要租户颁发者 URL,然后您关闭了这种类型的身份验证,API 仍然显示从中。清除此字段对我有用,但我从来没有这么想过,因为我不再使用 AD 身份验证。