Azure AD Android 入门

如果要开发桌面应用程序,Azure Active Directory (Azure AD) 可让你简单直接地使用用户的本地 Active Directory 帐户对其进行身份验证。 它还可以让应用程序安全地使用 Azure AD 保护的任何 Web API,例如 Office 365 API 或 Azure API。

对于需要访问受保护资源的 Android 客户端,Azure AD 提供 Active Directory 身份验证库 (ADAL)。 在本质上,ADAL 的唯一用途就是方便应用程序获取访问令牌。 为了演示这种简便性,我们将构建一个 Android 待办事项列表应用程序,它可以:

  • 使用 OAuth 2.0 身份验证协议获取用于调用待办事项列表 API 的访问令牌。
  • 获取用户的待办事项列表。
  • 将用户注销。

要开始,需要一个可在其中创建用户和注册应用程序的 Azure AD 租户。 如果没有租户,请了解如何获取租户

步骤 1:下载并运行 Node.js REST API TODO 示例服务器

Node.js REST API TODO 示例是为了与用于生成 Azure AD 的单租户待办事项 REST API 的现有示例配合使用而专门编写的。 这是本快速入门教程的先决条件。

有关如何设置此先决条件的信息,请参阅适用于 Node.js 的 Azure Active Directory 示例 REST API 服务

步骤 2:向 Azure AD 租户注册 Web API

Active Directory 支持添加两种类型的应用程序:

  • 为用户提供服务的 Web API
  • 访问这些 Web API 的应用程序(在 Web 或设备上运行)

在此步骤中,将注册你在本地运行的用于测试此示例的 Web API。 通常,此 Web API 是一个 REST 服务,它提供应用需要访问的功能。 Azure AD 可帮助保护任何终结点。

假设要注册前面引用的 TODO REST API。 但是,这同样适用于希望 Azure Active Directory 帮助保护的任何 Web API。

  1. 登录到 Azure 门户
  2. 在顶部栏上,单击帐户。 在“目录”列表中,选择要在其中注册应用程序的 Azure AD 租户。
  3. 在左窗格中,单击“所有服务”,并选择“Azure Active Directory”。
  4. 单击“应用注册”,并选择“添加”。
  5. 为应用程序输入一个友好的名称(例如“TodoListService”),选择“Web 应用程序和/或 Web API”,并单击“下一步”。
  6. 对于登录 URL,输入示例的基 URL。 默认情况下,它是 https://localhost:8080
  7. 单击“确定”完成注册。
  8. 仍然在 Azure 门户中,转到应用程序页面,找到应用程序 ID 值并复制它。 稍后在配置应用程序时需要此值。
  9. 从“设置” -> “属性”页中,更新应用 ID URI - 输入 https://<your_tenant_name>/TodoListService。 将 <your_tenant_name> 替换为 Azure AD 租户的名称。

步骤 3:注册示例 Android 本机客户端应用程序

在此示例中,必须注册 Web 应用程序。 这会允许应用程序与刚才注册的 Web API 进行通信。 除非注册了应用程序,否则 Azure AD 甚至可能会拒绝应用程序要求进行登录。 这是模型的安全功能的一部分。

假设要注册前面引用的示例应用程序。 但是,此过程同样适用于开发的任何应用。

Note

你可能想知道为何要将应用程序和 Web API 放在同一个租户中。 如你可能猜到的那样,可以构建一个从其他租户访问 Azure AD 中注册的外部 API 的应用程序。 如果这样做,系统会提示客户许可使用该应用程序中的 API。 适用于 iOS 的 Active Directory 身份验证库将负责处理此许可。 在了解更高级的功能后,将发现,这是从 Azure 和 Office 以及任何其他服务提供程序访问 Microsoft API 套件所需工作的重要部分。 现在,由于已将 Web API 和应用程序注册到同一个租户下,因此,不会看到任何许可提示。 如果只是在为自己的公司开发要使用的应用程序,则通常就是这种情况。

  1. 登录到 Azure 门户
  2. 在顶部栏上,单击帐户。 在“目录”列表中,选择要在其中注册应用程序的 Azure AD 租户。
  3. 在左窗格中,单击“所有服务”,并选择“Azure Active Directory”。
  4. 单击“应用注册”,并选择“添加”。
  5. 为应用程序输入一个友好的名称(例如“TodoListClient-Android”),选择“本机客户端应用程序”,并单击“下一步”。
  6. 对于“重定向 URI”,输入 http://TodoListClient。 单击“完成” 。
  7. 在应用程序页面中,找到应用程序 ID 值并复制它。 稍后在配置应用程序时需要此值。
  8. 在“设置”页上,选择“所需权限”,并选择“添加”。 找到并选择 TodoListService,在“委派的权限”下添加“访问 TodoListService”权限,并单击“完成”。

若要使用 Maven 进行构建,可以在顶层使用 pom.xml:

  1. 将此存储库克隆到选择的目录:

    $ git clone git@github.com:AzureADSamples/NativeClient-Android.git

  2. 遵循先决条件部分中的步骤为 Android 设置 Maven 环境
  3. 使用 SDK 19 设置模拟器。
  4. 转到存储库克隆到的根文件夹。
  5. 运行以下命令:mvn clean install
  6. 将目录切换到快速入门项目示例:cd samples\hello
  7. 运行以下命令:mvn android:deploy android:run

    应该会看到应用正在启动。

  8. 输入测试用户凭据进行尝试。

除了 AAR 包以外,还将提交 JAR 包。

步骤 4:下载 Android ADAL 并将其添加到 Eclipse 工作区

我们提供了多个选项以方便你在 Android 项目中使用 ADAL:

  • 可以使用源代码将此库导入到 Eclipse 并链接到应用程序。
  • 如果使用的是 Android Studio,则可以使用 AAR 包格式并引用二进制文件。

选项 1:源 Zip

若要下载源代码副本,请单击页面右侧的单击“下载 ZIP”。 也可以从 GitHub 下载

选项 2:通过 Git 获取源代码

若要通过 Git 获取 SDK 的源代码,请键入:

git clone git@github.com:AzureAD/azure-activedirectory-library-for-android.git
cd ./azure-activedirectory-library-for-android/src

选项 3:通过 Gradle 获取二进制文件

可以从 Maven 中心存储库获取二进制文件。 在 Android Studio 中,可以按以下方式在项目中包括 AAR 包:

repositories {
    mavenCentral()
    flatDir {
        dirs 'libs'
    }
    maven {
        url "YourLocalMavenRepoPath\\.m2\\repository"
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile('com.microsoft.aad:adal:1.1.1') {
        exclude group: 'com.android.support'
    } // Recent version is 1.1.1
}

选项 4:通过 Maven 获取 AAR

如果使用的是 M2Eclipse 插件,可以在 pom.xml 文件中指定依赖关系:

<dependency>
    <groupId>com.microsoft.aad</groupId>
    <artifactId>adal</artifactId>
    <version>1.1.1</version>
    <type>aar</type>
</dependency>

选项 5:libs 文件夹中的 JAR 包

可以从 maven 存储库获取 JAR 文件并将其放入项目的 libs 文件夹中。 同样,还需要将所需资源复制到项目,因为 JAR 包未包括它们。

步骤 5:在项目中添加对 Android ADAL 的引用

  1. 添加对项目的引用,并将其指定为 Android 库。 如果不确定如何执行此操作,可以在 Android Studio 站点上获取详细信息。
  2. 在项目设置中添加用于调试的项目依赖关系。
  3. 更新项目的 AndroidManifest.xml 文件以包括:

     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <application
         android:allowBackup="true"
         android:debuggable="true"
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
    
         <activity
             android:name="com.microsoft.aad.adal.AuthenticationActivity"
             android:label="@string/title_login_hello_app" >
         </activity>
         ....
     <application/>
    
  4. 在主要活动中创建 AuthenticationContext 的实例。 有关此调用的详细信息不在本主题的范围内,但可以通过查看 Android 本机客户端示例来快速入门。 在下面的示例中,SharedPreferences 是默认缓存,机构采用 https://login.partner.microsoftonline.cn/yourtenant.partner.onmschina.cn 形式:

    mContext = new AuthenticationContext(MainActivity.this, authority, true); // mContext is a field in your activity

  5. 复制此代码块,以便在用户输入凭据并收到授权代码后处理 AuthenticationActivity 的结束:

     @Override
      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
          super.onActivityResult(requestCode, resultCode, data);
          if (mContext != null) {
             mContext.onActivityResult(requestCode, resultCode, data);
          }
      }
    
  6. 若要请求令牌,可以定义一个回调:

     private AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
    
         @Override
         public void onError(Exception exc) {
             if (exc instanceof AuthenticationException) {
                 textViewStatus.setText("Cancelled");
                 Log.d(TAG, "Cancelled");
             } else {
                 textViewStatus.setText("Authentication error:" + exc.getMessage());
                 Log.d(TAG, "Authentication error:" + exc.getMessage());
             }
         }
    
         @Override
         public void onSuccess(AuthenticationResult result) {
             mResult = result;
    
             if (result == null || result.getAccessToken() == null
                     || result.getAccessToken().isEmpty()) {
                 textViewStatus.setText("Token is empty");
                 Log.d(TAG, "Token is empty");
             } else {
                 // request is successful
                 Log.d(TAG, "Status:" + result.getStatus() + " Expired:"
                         + result.getExpiresOn().toString());
                 textViewStatus.setText(PASSED);
             }
         }
     };
    
  7. 最后,使用该回调来请求令牌:

    mContext.acquireToken(MainActivity.this, resource, clientId, redirect, user_loginhint, PromptBehavior.Auto, "", callback);

下面是参数说明:

  • resource 是必需的,它是你尝试访问的资源。
  • clientid 是必需的,它来自 Azure AD。
  • RedirectUri 不是必需的,对于 acquireToken 调用,不需要提供此参数。 可以将其设置为包名称。
  • PromptBehavior 可帮助请求凭据以跳过缓存和 Cookie。
  • 在交换令牌的授权代码后,将调用 callback。 它具有一个包含访问令牌、过期日期和 ID 令牌信息的 AuthenticationResult 对象。
  • acquireTokenSilent 是可选的。 可以调用它来处理缓存和令牌刷新。 它还提供同步版本。 它接受 userId 作为参数。

      mContext.acquireTokenSilent(resource, clientid, userId, callback );
    

使用本演练后,应该会获得与 Azure Active Directory 成功集成所需的项目。 有关此工作的更多示例,请访问 GitHub 上的 AzureADSamples/ 存储库。

重要信息

自定义

应用程序资源可以覆盖库项目资源。 这是在构建应用程序时发生的。 因此,可以使用所需的方式自定义身份验证活动布局。 务必要保留 ADAL 使用的控件的 ID (Webview)。

代理

Microsoft Intune 公司门户应用提供了代理组件。 帐户是在 AccountManager 中创建的。 帐户类型为“com.microsoft.workaccount”。 AccountManager 仅允许单个 SSO 帐户。 针对其中一个应用完成设备质询后,它会为此用户创建一个 SSO Cookie。

如果在此验证器中创建了一个用户帐户并且你选择不跳过代理帐户,则 ADAL 将使用代理帐户。 可以使用以下方法跳过代理用户:

AuthenticationSettings.Instance.setSkipBroker(true);

需要注册一个特殊的 RedirectUri 供代理使用。 RedirectUri 的格式为 msauth://packagename/Base64UrlencodedSignature。 可以使用脚本 brokerRedirectPrint.ps1 或者使用 API 调用 mContext.getBrokerRedirectUri 获取应用的 RedirectUri。 签名与签名证书相关。

当前代理模型是针对单个用户的。 AuthenticationContext 提供了用于获取代理用户的 API 方法。

String brokerAccount = mContext.getBrokerUser(); //Broker user is returned if account is valid.

应用清单应当具有以下权限才能使用 AccountManager 帐户。 有关详细信息,请参阅 Android 站点上的 AccountManager 信息

  • GET_ACCOUNTS
  • USE_CREDENTIALS
  • MANAGE_ACCOUNTS

机构 URL 和 AD FS

Active Directory 联合身份验证服务 (AD FS) 不会识别为生产 STS,因此,需要关闭实例发现,并在 AuthenticationContext 构造函数中传递 false。

机构 URL 需要 STS 实例和租户名称

查询缓存项

ADAL 在 SharedPreferences 中提供了默认缓存,以及一些简单的缓存查询函数。 可以使用以下命令从 AuthenticationContext 中获取当前缓存:

ITokenCacheStore cache = mContext.getCache();

还可以提供缓存实现(如果想要对其进行自定义)。

mContext = new AuthenticationContext(MainActivity.this, authority, true, yourCache);

提示行为

ADAL 提供了用于指定提示行为的选项。 如果刷新令牌无效并且需要用户凭据,则 PromptBehavior.Auto 会显示 UI。 PromptBehavior.Always 会跳过缓存使用并始终显示 UI。

来自缓存和刷新的无提示令牌请求

静默令牌请求不使用 UI 弹出并且不要求执行操作。 它将从缓存返回一个令牌(如果有)。 如果令牌已过期,此方法会尝试对其进行刷新。 如果刷新令牌已过期或失败,它会返回 AuthenticationException。

Future<AuthenticationResult> result = mContext.acquireTokenSilent(resource, clientid, userId, callback );

还可以使用此方法执行同步调用。 可以将回调设置为 null,或使用 acquireTokenSilentSync。

诊断

下面是用来诊断问题的信息的主要来源:

  • 异常
  • 日志
  • 网络跟踪

请注意,相关性 ID 是在库中进行诊断的关键所在。 如果想要在代码中将 ADAL 请求关联到其他操作,可以基于每个请求设置相关性 ID。 如果未设置相关性 ID,则 ADAL 将生成一个随机相关性 ID。 然后,会为所有日志消息和网络调用标上该相关性 ID。 每发出一个请求,自我生成的 ID 都会更改。

异常

异常是首选的诊断信息。 我们将尝试提供有用的错误消息。 如果发现某个错误消息没有作用,请记录相应的问题并告诉我们。 请提供设备信息,例如型号和 SDK 编号。

日志

可以将库配置为生成有助于诊断问题的日志消息。 要配置日志记录,可以执行以下调用以配置一个回调,ADAL 将使用该回调来移交它所生成的每条日志消息。

Logger.getInstance().setExternalLogger(new ILogger() {
    @Override
    public void Log(String tag, String message, String additionalMessage, LogLevel level, ADALError errorCode) {
    ...
    // You can write this to log file depending on level or error code.
    writeToLogFile(getApplicationContext(), tag +":" + message + "-" + additionalMessage);
    }
}

可以将消息写入到自定义日志文件,如以下代码所示。 遗憾的是,没有标准的方法可从设备中获取日志。 有些服务可帮助你实现此目的。 也可以创造自己的方法,例如,将文件发送到服务器。

private syncronized void writeToLogFile(Context ctx, String msg) {
   File directory = ctx.getDir(ctx.getPackageName(), Context.MODE_PRIVATE);
   File logFile = new File(directory, "logfile");
   FileOutputStream outputStream = new FileOutputStream(logFile, true);
   OutputStreamWriter osw = new OutputStreamWriter(outputStream);
   osw.write(msg);
   osw.flush();
   osw.close();
}

下面是日志记录级别:

  • Error(异常)
  • Warn(警告)
  • Info(信息用途)
  • Verbose(更多详细信息)

可按如下所述设置日志级别:

Logger.getInstance().setLogLevel(Logger.LogLevel.Verbose);

除了发送到任何自定义日志回调以外,所有日志消息还会发送到 logcat。 可以如下所示将日志从 logcat 写入到文件中:

adb logcat > "C:\logmsg\logfile.txt"

有关 adb 命令的详细信息,请参阅 Android 站点上的 logcat 信息

网络跟踪

可以使用各种工具来捕获 ADAL 生成的 HTTP 流量。 如果熟悉 OAuth 协议或者需要向 Microsoft 或其他支持渠道提供诊断信息,这会十分有用。

Fiddler 是最方便的 HTTP 跟踪工具。 可以使用以下链接设置该工具以正确记录 ADAL 网络流量。 要使 Fiddler 或 Charles 之类的跟踪工具发挥作用,必须对其进行配置以记录未加密的 SSL 流量。

Note

以这种方式生成的跟踪可能包含高特权信息,例如访问令牌、用户名和密码。 如果使用的是生产帐户,请不要与第三方共享这些跟踪。 如果需要向某人提供跟踪以便获得支持,请使用一个临时帐户再现问题,临时帐户包含你不介意共享的用户名和密码。

对话模式

无活动的 acquireToken 方法支持对话提示。

加密

默认情况下,ADAL 会加密令牌并将其存储在 SharedPreferences 中。 可以查看 StorageHelper 类以了解详细信息。 Android 引入了 Android Keystore for 4.3 (API 18) 来安全地存储私钥。 ADAL 为 API 18 和更高版本使用该组件。 如果希望将 ADAL 用于较低的 SDK 版本,需要在 AuthenticationSettings.INSTANCE.setSecretKey 中提供一个密钥。

OAuth2 持有者质询

AuthenticationParameters 类提供了通过 OAuth2 持有者质询获取 authorization_uri 的功能。

WebView 中的会话 Cookie

关闭应用后,Android WebView 不会清除会话 cookie。 可以使用以下示例代码处理此 cookie:

CookieSyncManager.createInstance(getApplicationContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeSessionCookie();
CookieSyncManager.getInstance().sync();

有关 cookie 的详细信息,请参阅 Android 站点上的 CookieSyncManager 信息

资源重写

ADAL 库包含以下 ProgressDialog 消息的英文字符串。 如果需要本地化的字符串,应用程序应覆盖这些英文字符串。

 <string name="app_loading">Loading...</string>
 <string name="broker_processing">Broker is processing</string>
 <string name="http_auth_dialog_username">Username</string>
 <string name="http_auth_dialog_password">Password</string>
 <string name="http_auth_dialog_title">Sign In</string>
 <string name="http_auth_dialog_login">Login</string>
 <string name="http_auth_dialog_cancel">Cancel</string>

NTLM 对话框

ADAL 版本 1.1.0 支持通过 WebViewClient 中的 onReceivedHttpAuthRequest 事件处理的 NTLM 对话框。 可以为该对话框自定义布局和字符串。

跨应用 SSO

了解如何使用 ADAL 在 Android 上启用跨应用 SSO

其他资源

获取关于我们产品的安全更新

建议发生安全事件时获取相关通知,方法是访问此页并订阅“安全公告通知”。