【问题标题】:PHP Azure Active Directory API Access using Client Credentials (X.509 certificate)使用客户端凭据(X.509 证书)的 PHP Azure Active Directory API 访问
【发布时间】:2019-01-02 00:30:41
【问题描述】:

我正在开发一个 PHP 脚本来登录 Microsoft 的 365 API 并扫描用户的电子邮件以查找与 CRM 中的条目匹配的内容,这样我们就可以将 Outlook 中的电子邮件与 CRM 中的人员链接起来。

我已经通过 Azure 获得了正常的 client_secret 登录方法,因此我在 Azure 门户中设置了一个 webapp 条目,我可以获取令牌并使用 AD 用户端点 (https://graph.windows.net/<tenant>/users?api-version=1.6) 获取用户列表

但是,要获取用户的电子邮件,我需要使用此处详述的 X.509 证书方法进行身份验证:

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-client-creds

但是,尽管过去几个小时在 Google 上搜索过,但我找不到用于执行此操作的示例 PHP,而且我也在 Linux 中,因此仅生成兼容的 X509 证书并不简单(我的Windows VM,因为大多数示例使用的 makecert 程序似乎不再可用!)

但是,使用临时 30 天证书,我似乎已经完成了这一点,因为它已成功上传到 Azure 门户。

无论如何,是否有人拥有或知道任何指向 PHP 代码的链接,用于使用客户端断言方法提交访问令牌请求?

特别是如何生成 JWT 值(我已阅读文章 https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials#code-sample,但它谈到了提交各种索赔值,而没有说明您实际从哪里获取数据或如何计算它。

我是一名经验丰富的开发人员,但在与 Azure 交谈方面有点新手。

提前致谢

【问题讨论】:

  • 为什么需要使用 X.509 证书进行身份验证?我的理解是,常规的客户机密会起作用。另外,我建议使用 Microsoft Graph API 而不是 Azure AD Graph API,因为 a) 不再建议使用 AAD Graph(有几个例外)和 b) 您可以从 MS Graph API 获取用户信息和电子邮件。
  • 嗨,很抱歉造成混乱,这可能是因为 a) 微软的 API 令人困惑,b) 我没有正确理解术语。我正在使用 MS Graph API,但由于脚本在后台运行,它使用 Azure API 来获取访问令牌,因为它支持通过客户端凭据流对整个组织进行管理员级别的访问,没有这个每个用户都会有登录该应用程序以授予其访问其电子邮件的权限。这就是它需要 X.509 证书的原因;如果没有它,Microsoft 不允许访问组织中其他用户的邮件。
  • 您可以在 MS Graph API 上授予应用程序读取所有用户电子邮件的权限。
  • 我有,但要访问用户电子邮件,您需要使用 X.509 证书。我现在已经设法使用它获取访问令牌,但是对 Outlook 端点的调用现在失败,出现 401 未经授权的错误:/ 观众声明值无效 'aud'。";error_category="invalid_resource"跨度>
  • 你在呼叫哪个端点?

标签: php azure oauth-2.0 azure-active-directory


【解决方案1】:

好的,我终于设法做到了,所以我将发布一些内容以希望对其他人有所帮助。

快速概览:

  • 我已经在 Azure 门户中设置了我的应用程序,确保授予它对“密钥”部分下的 Office API 图形 API 和 Active Directory API 的访问权限,并确保单击“授予权限”按钮。
  • 使用 Auth 端点 https://login.windows.net/#tenant#/oauth2/authorize 我可以正确授权应用程序,确保将“prompt=admin_consent”添加到 Auth URL 的末尾以获得管理员同意,而不是用户同意 - 此时我正在使用 client_id 和 client_secret 进行身份验证
  • 我可以访问 Active Directory 端点以获取 Active Directory 中的用户列表。

我遇到的主要问题是我试图通过 Outlook API 访问用户的电子邮件,我可以正常阅读我的电子邮件,但尝试阅读其他人的电子邮件会导致 401 错误。

事实证明,正如预期的那样。如果您获得带有 client_secret 值(即密码)的身份验证令牌,则您只能在 Outlook API 中访问您自己的详细信息。在您尝试访问其他任何人的那一刻,您会收到拒绝访问错误。

解决这个问题的方法是创建一个 X.509 密钥并使用它来进行身份验证,而不是 client_secret。

但是关于如何在 PHP 中执行此操作的信息非常少,我就是这样做的:

好的,首先创建 X.509 证书。我在这里遵循了本指南:https://github.com/Azure/azure-iot-sdk-c/blob/master/tools/CACertificates/CACertificateOverview.md

然后我需要弄清楚如何为您在获取身份验证令牌时传递的 client_assertion 参数创建 JWT 代码。

有一个出色的 PHP 库,名为 Firebase,其中包含一个 JWT 编码器 - https://github.com/firebase/php-jwt 可通过 composer 安装,非常容易安装。

然后我需要从 Azure SDK 破解一个类来管理证书,但首先我必须将 pem 证书转换为 pfx,我使用以下命令(来自与 cert_gen 相同的目录.sh 文件)

openssl pkcs12 -export -out certs/azure-iot-test-only.chain.pfx -inkey private/azure-iot-test-only.intermediate.key.pem -in certs/azure-iot-test-only.chain.ca.cert.pem -certfile certs/azure-iot-test-only.chain.ca.cert.pem

https://github.com/Azure/azure-sdk-for-php 是您需要的 SDK,文件是 AzureAdClientAsymmetricKey.php

因此,将所有这些整合到一些代码中 - 这不是为可运行而设计的,它已从我的系统中删除,但它应该有望为您指明正确的方向。

在我的应用程序中,我创建了两个身份验证令牌,一个用于 Outlook API,一个用于图形 API,因此您会看到两个不同的范围在使用中。

    $result = [
            'uri'       => str_replace('#tenant#',$this->tenantId,'https://login.windows.net/#tenant#/oauth2/authorize'),
            'params'    => [
                'response_type'     => 'code',
                'client_id'         => $this->clientId, // the app client id
                'grant_type'        => 'client_credentials',
                'scope'             => $this->getScopeParam($scope),
            ],
        ];

        $result['params']['tenant'] = $this->tenantId;
        $result['params']['code'] = $this->azureAuthCode; // THe code returned from the admin authorisation

        $pfxFileName = '/path/to/certs/azure-iot-test-only.chain.pfx';
        $pfxPassword = '1234';

        if ((!$cert_store = file_get_contents($pfxFileName)) ||
            (!openssl_pkcs12_read($cert_store, $cert_info, $pfxPassword))) {
            $this->logger->addError("Unable to read the cert file");
            return $result;
        }

        $result['params']['resource'] = $scope == 'outlook' ? 'https://outlook.office.com' : 'https://graph.microsoft.com';

        $credentials = new AdClientAsymmetricKey($this->clientId,$cert_info);

        // We need to create the JWT for the authentication
        $head = [];
        $head['x5t'] = $credentials->getFingerprint();
        $head['x5c'] = [ $credentials->getCertificate() ];

        $token = [];
        $token['aud'] = $result['uri'];
        $token['sub'] = $credentials->getClientId();
        $token['iss'] = $credentials->getClientId();
        $token['nbf'] = (string)((new \DateTime("now", new \DateTimeZone('UTC')))->getTimestamp() - 60);
        $token['exp'] = (string)((new \DateTime("now", new \DateTimeZone('UTC')))->getTimestamp() + 520);

        $result['params']['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
        $result['params']['client_assertion'] = JWT::encode($token, $credentials->getPrivateKey(), 'RS256', null, $head);
        return $result;

我遇到的最后一个问题是当我在成功获取令牌后尝试访问邮件时出现资源无效错误。事实证明,这非常简单而且很有帮助,没有记录在微软的文档中。你在上面的代码中看到,有一行...

$result['params']['resource'] = $scope == 'outlook' ? 'https://outlook.office.com' : 'https://graph.microsoft.com';

这是关键参数,因为它设置令牌可以访问的资源,$scope 被传递到上面的函数中,并且是 Outlook 或图形,以设置对相关 API 端点的请求。

无论如何,我希望这对某人有所帮助,我花了大约 8 个小时才弄清楚这一点!

【讨论】:

  • “可通过 composer 安装,非常容易安装” ... 除了在生产环境中,composer 是一个巨大的安全漏洞,而且是 github 的一大亮点。
猜你喜欢
  • 2019-04-28
  • 1970-01-01
  • 2016-03-28
  • 1970-01-01
  • 2011-10-25
  • 1970-01-01
  • 2021-04-01
  • 2013-04-15
  • 1970-01-01
相关资源
最近更新 更多