在本文中,将用户身份验证添加到 使用 Microsoft Graph 生成 PHP 应用中创建的应用程序。 然后,使用 Microsoft Graph 用户 API 获取经过身份验证的用户。
添加用户身份验证
Microsoft Graph SDK 包括基于 PHP 联盟 OAuth2 客户端的身份验证提供程序。 但是,对于本教程,你将使用 设备代码流 来获取访问令牌。 包含的身份验证提供程序不实现此流,因此需要实现自定义访问令牌提供程序。
创建访问令牌提供程序
在项目的根目录中创建一个名为 DeviceCodeTokenProvider.php 的新文件。 添加以下代码。
<?php
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
use GuzzleHttp\Client;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use Microsoft\Kiota\Abstractions\Authentication\AccessTokenProvider;
use Microsoft\Kiota\Abstractions\Authentication\AllowedHostsValidator;
class DeviceCodeTokenProvider implements AccessTokenProvider {
private string $clientId;
private string $tenantId;
private string $scopes;
private AllowedHostsValidator $allowedHostsValidator;
private string $accessToken;
private Client $tokenClient;
public function __construct(string $clientId, string $tenantId, string $scopes) {
$this->clientId = $clientId;
$this->tenantId = $tenantId;
$this->scopes = $scopes;
$this->allowedHostsValidator = new AllowedHostsValidator();
$this->allowedHostsValidator->setAllowedHosts([
"graph.microsoft.com",
"graph.microsoft.us",
"dod-graph.microsoft.us",
"graph.microsoft.de",
"microsoftgraph.chinacloudapi.cn"
]);
$this->tokenClient = new Client();
}
public function getAuthorizationTokenAsync(string $url, array $additionalAuthenticationContext = []): Promise {
$parsedUrl = parse_url($url);
$scheme = $parsedUrl["scheme"] ?? null;
if ($scheme !== 'https' || !$this->getAllowedHostsValidator()->isUrlHostValid($url)) {
return new FulfilledPromise(null);
}
// If we already have a user token, just return it
// Tokens are valid for one hour, after that it needs to be refreshed
if (isset($this->accessToken)) {
return new FulfilledPromise($this->accessToken);
}
// https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-device-code
$deviceCodeRequestUrl = 'https://login.microsoftonline.com/'.$this->tenantId.'/oauth2/v2.0/devicecode';
$tokenRequestUrl = 'https://login.microsoftonline.com/'.$this->tenantId.'/oauth2/v2.0/token';
// First POST to /devicecode
$deviceCodeResponse = json_decode($this->tokenClient->post($deviceCodeRequestUrl, [
'form_params' => [
'client_id' => $this->clientId,
'scope' => $this->scopes
]
])->getBody()->getContents());
// Display the user prompt
print($deviceCodeResponse->message.PHP_EOL);
// Response also indicates how often to poll for completion
// And gives a device code to send in the polling requests
$interval = (int)$deviceCodeResponse->interval;
$device_code = $deviceCodeResponse->device_code;
// Do polling - if attempt times out the token endpoint
// returns an error
while (true) {
sleep($interval);
// POST to the /token endpoint
$tokenResponse = $this->tokenClient->post($tokenRequestUrl, [
'form_params' => [
'client_id' => $this->clientId,
'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
'device_code' => $device_code
],
// These options are needed to enable getting
// the response body from a 4xx response
'http_errors' => false,
'curl' => [
CURLOPT_FAILONERROR => false
]
]);
if ($tokenResponse->getStatusCode() == 200) {
// Return the access_token
$responseBody = json_decode($tokenResponse->getBody()->getContents());
$this->accessToken = $responseBody->access_token;
return new FulfilledPromise($responseBody->access_token);
} else if ($tokenResponse->getStatusCode() == 400) {
// Check the error in the response body
$responseBody = json_decode($tokenResponse->getBody()->getContents());
if (isset($responseBody->error)) {
$error = $responseBody->error;
// authorization_pending means we should keep polling
if (strcmp($error, 'authorization_pending') != 0) {
return new RejectedPromise(
new Exception('Token endpoint returned '.$error, 100));
}
}
}
}
}
public function getAllowedHostsValidator(): AllowedHostsValidator {
return $this->allowedHostsValidator;
}
}
?>
配置 Graph 客户端以用于用户身份验证
现在, DeviceCodeTokenProvider
使用 类通过 设备代码流请求访问令牌。
在项目的根目录中创建一个名为 GraphHelper.php 的新文件。 添加以下代码。
<?php class GraphHelper { } ?>
在 PHP 标记中添加以下
using
语句。use Microsoft\Graph\Generated\Models; use Microsoft\Graph\Generated\Users\Item\MailFolders\Item\Messages\MessagesRequestBuilderGetQueryParameters; use Microsoft\Graph\Generated\Users\Item\MailFolders\Item\Messages\MessagesRequestBuilderGetRequestConfiguration; use Microsoft\Graph\Generated\Users\Item\SendMail\SendMailPostRequestBody; use Microsoft\Graph\Generated\Users\Item\UserItemRequestBuilderGetQueryParameters; use Microsoft\Graph\Generated\Users\Item\UserItemRequestBuilderGetRequestConfiguration; use Microsoft\Graph\GraphRequestAdapter; use Microsoft\Graph\GraphServiceClient; use Microsoft\Kiota\Abstractions\Authentication\BaseBearerTokenAuthenticationProvider; require_once 'DeviceCodeTokenProvider.php';
将以下代码添加到
GraphHelper
类。private static string $clientId = ''; private static string $tenantId = ''; private static string $graphUserScopes = ''; private static DeviceCodeTokenProvider $tokenProvider; private static GraphServiceClient $userClient; public static function initializeGraphForUserAuth(): void { GraphHelper::$clientId = $_ENV['CLIENT_ID']; GraphHelper::$tenantId = $_ENV['TENANT_ID']; GraphHelper::$graphUserScopes = $_ENV['GRAPH_USER_SCOPES']; GraphHelper::$tokenProvider = new DeviceCodeTokenProvider( GraphHelper::$clientId, GraphHelper::$tenantId, GraphHelper::$graphUserScopes); $authProvider = new BaseBearerTokenAuthenticationProvider(GraphHelper::$tokenProvider); $adapter = new GraphRequestAdapter($authProvider); GraphHelper::$userClient = GraphServiceClient::createWithRequestAdapter($adapter); }
将 main.php 中的空
initializeGraph
函数替换为以下内容。function initializeGraph(): void { GraphHelper::initializeGraphForUserAuth(); }
此代码从 .env 文件加载信息,并初始化两个 DeviceCodeTokenProvider
属性:对象和 GraphServiceClient
对象。 对象 DeviceCodeTokenProvider
用于请求访问令牌,对象 GraphServiceClient
用于调用 Microsoft Graph。
测试设备代码流
接下来,添加代码以从 GraphHelper
获取访问令牌。
将以下函数添加到
GraphHelper
类。public static function getUserToken(): string { return GraphHelper::$tokenProvider ->getAuthorizationTokenAsync('https://graph.microsoft.com')->wait(); }
将 main.php 中的空
displayAccessToken
函数替换为以下内容。function displayAccessToken(): void { try { $token = GraphHelper::getUserToken(); print('User token: '.$token.PHP_EOL.PHP_EOL); } catch (Exception $e) { print('Error getting access token: '.$e->getMessage().PHP_EOL.PHP_EOL); } }
生成并运行应用。 当系统提示输入选项时,请输入
1
。 应用程序显示 URL 和设备代码。$ php main.php PHP Graph Tutorial Please choose one of the following options: 0. Exit 1. Display access token 2. List my inbox 3. Send mail 4. Make a Graph call 1 To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code RB2RUD56D to authenticate.
打开浏览器并浏览到显示的 URL。 输入提供的代码并登录。
重要
请注意浏览到
https://microsoft.com/devicelogin
时登录到浏览器的任何现有Microsoft 365 个帐户。 使用浏览器功能(如配置文件、来宾模式或专用模式)确保作为要用于测试的帐户进行身份验证。完成后,返回到应用程序以查看访问令牌。
提示
仅出于验证和调试目的,只能) 使用 Microsoft 的联机令牌分析程序在 中https://jwt.ms解码工作或学校帐户的用户访问令牌 (。 如果在调用 Microsoft Graph 时遇到令牌错误,则分析令牌可能很有用。 例如,验证令牌中的声明是否
scp
包含预期的 Microsoft Graph 权限范围。
获取用户
配置身份验证后,可以进行第一个Microsoft图形 API调用。 添加代码以获取经过身份验证的用户的姓名和电子邮件地址。
将以下代码添加到
GraphHelper
类。public static function getUser(): Models\User { $configuration = new UserItemRequestBuilderGetRequestConfiguration(); $configuration->queryParameters = new UserItemRequestBuilderGetQueryParameters(); $configuration->queryParameters->select = ['displayName','mail','userPrincipalName']; return GraphHelper::$userClient->me()->get($configuration)->wait(); }
将 main.php 中的空
greetUser
函数替换为以下内容。function greetUser(): void { try { $user = GraphHelper::getUser(); print('Hello, '.$user->getDisplayName().'!'.PHP_EOL); // For Work/school accounts, email is in Mail property // Personal accounts, email is in UserPrincipalName $email = $user->getMail(); if (empty($email)) { $email = $user->getUserPrincipalName(); } print('Email: '.$email.PHP_EOL.PHP_EOL); } catch (Exception $e) { print('Error getting user: '.$e->getMessage().PHP_EOL.PHP_EOL); } }
如果现在运行应用,则登录后,应用将按名称欢迎你。
Hello, Megan Bowen!
Email: MeganB@contoso.com
代码说明
请考虑 函数中的 getUser
代码。 这只是几行,但有一些关键细节需要注意。
访问“me”
函数生成对 获取用户 API 的请求。 可通过两种方式访问此 API:
GET /me
GET /users/{user-id}
在这种情况下,代码将调用 GET /me
API 终结点。 此终结点是在不知道其用户 ID 的情况下获取经过身份验证的用户的快捷方式。
注意
GET /me
由于 API 终结点获取经过身份验证的用户,因此它仅适用于使用用户身份验证的应用。 仅限应用的身份验证应用无法访问此终结点。
请求特定属性
函数使用 $select 查询参数 来指定它所需的属性集。
强类型返回类型
函数从 API 的 JSON 响应返回 User
反序列化的对象。 由于代码使用 $select
,因此只有请求的属性在返回 User
的对象中具有值。 所有其他属性都具有默认值。