好吧,我终于做到了,所以我会发布一些东西来帮助别人。
快速概述:
-
我已经在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个小时才弄清楚真相!