代码之家  ›  专栏  ›  技术社区  ›  Steve Childs

使用客户端凭据(X.509证书)的PHP Azure活动目录API访问

  •  1
  • Steve Childs  · 技术社区  · 8 年前

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

    我已经通过Azure使用了普通的客户端秘密登录方法,因此我在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 Cert并不简单(在我的Windows VM上也不是这样,因为大多数示例使用的makecert程序似乎不再可用!)

    不过,使用一个30天的临时证书,我似乎已经成功地将其上传到Azure门户网站。

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

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

    我是一个经验丰富的开发人员,但当谈到与Azure的对话时,我有点生疏。

    多谢提前

    1 回复  |  直到 8 年前
        1
  •  1
  •   Steve Childs    8 年前

    好吧,我终于做到了,所以我会发布一些东西来帮助别人。

    快速概述:

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

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

    事实证明,这是意料之中的。如果您使用客户机机密值(即密码)获取身份验证令牌,则只能在Outlook API中访问自己的详细信息。当您尝试访问其他人的时,会出现拒绝访问错误。

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

    但是关于如何在PHP中实现这一点的信息非常少,我就是这样做到的:

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

    然后我需要解决如何为获取auth令牌时传递的client_断言参数创建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,文件是azureadclientasymetrickey.php

    所以,把这些放在一起形成一些代码-这不是设计成可运行的,它从我的系统中被切掉了,但它应该希望能给你指明正确的方向。

    在我的应用程序中,我创建了两个auth令牌,一个用于outlook API,另一个用于graphapi,因此您将看到两个不同的作用域正在使用。

        $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或graph,用于设置对相关API端点的请求。

    不管怎样,我希望这能帮上忙,我花了8个小时才弄清楚真相!