使用客户端凭据(X.509证书)的PHP Azure Active Directory API访问

时间:2018-07-25 08:48:06

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

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

我已经可以通过Azure使用正常的client_secret登录方法,因此我已经在Azure门户中设置了一个webapp条目,并且可以使用AD用户终结点({{3} })

但是,要获取用户的电子邮件,我需要使用X.509证书方法进行身份验证,如下所示:

https://graph.windows.net/<tenant>/users?api-version=1.6

但是,尽管在过去的几个小时里一直在搜索Google,但我找不到用于执行此操作的示例PHP,而且我也在Linux中,因此仅生成兼容的X509 Cert并不是一件容易的事(我也没有这么做) Windows VM,似乎大多数示例使用的makecert程序不再可用!)

但是,使用30天的临时证书,由于它已成功上传到Azure门户中,因此我似乎已经完成了该任务。

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

特别是如何生成JWT值(我已经读过文章https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-client-creds,但是它谈到了提交各种声明值而没有说您实际从何处获取数据或如何处理数据。

>

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

非常感谢

1 个答案:

答案 0 :(得分:1)

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

快速概述:

  • 我已经在Azure门户中设置了我的应用程序,请确保在“密钥”部分下授予其对Office API Graph API和Active Directory API的访问权限,并确保单击“授予权限”按钮。
  • 使用Auth端点https://login.windows.net/#tenant#/oauth2/authorize,我可以正确授权应用程序,确保在Auth URL的末尾添加'prompt = admin_consent'以获得管理员同意,而不是用户同意-在这一点我正在使用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

然后,我需要弄清楚如何为获取auth令牌时传递的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;

我遇到的最后一个问题是成功获取令牌后尝试访问邮件是无效的资源错误。事实证明,这简直是愚蠢而又很有帮助,没有在Microsoft的文档中进行记录。您可以在上面的代码中看到一行...

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

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

无论如何,我希望这可以帮助某人,这花了我大约8个小时才走到最深处!

相关问题