如何通过Client Credentials流查询租户日历?解决受众无效问题
首先,你的问题核心出在获取访问令牌时指定的Resource参数错误,加上一些不必要的配置步骤,导致令牌的受众(Audience)与Microsoft Graph API不匹配,进而引发权限验证失败。下面一步步帮你解决:
1. 修正访问令牌的Resource参数
你当前获取令牌时指定的resource是https://graph.windows.net,这是旧版的Azure AD Graph API的资源标识符,而你要调用的是新版的Microsoft Graph API,正确的Resource应该是https://graph.microsoft.com。
这个参数直接决定了令牌的受众(aud字段),如果令牌的aud是https://graph.windows.net,调用https://graph.microsoft.com的API自然会提示"Invalid Audience"。
修正后的PHP代码如下:
$provider = new \TheNetworg\OAuth2\Client\Provider\Azure([ 'clientId' => env('OAUTH_APP_ID'), 'clientSecret' => env('OAUTH_APP_PASSWORD'), 'tenant' => env('OAUTH_TENANT_ID'), // 这里填你的Directory (tenant) ID ]); // 客户端凭证流不需要redirectUri,所以可以去掉这个参数 $token = $provider->getAccessToken('client_credentials', [ 'resource' => 'https://graph.microsoft.com', // 重点修正这里 ]); // 设置API基础地址为Microsoft Graph v1.0 $provider->urlAPI = "https://graph.microsoft.com/v1.0/"; $provider->resource = "https://graph.microsoft.com/"; return $token;
获取到的令牌解码后(可以用任意JWT解码工具查看),aud字段应该是https://graph.microsoft.com,这就匹配了API的受众要求。
2. 移除不必要的"暴露API"配置
你步骤4中"暴露API"并设置Application ID URI的操作是多余的——这一步是用于让其他应用调用你的应用提供的API,而不是让你的应用调用Microsoft Graph API。完全可以删除这部分配置,避免混淆。
3. 确认应用权限已获得管理员同意
你已经给应用授予了https://graph.microsoft.com/Calendars.ReadWrite的应用权限,但要注意:应用权限必须由租户管理员明确授予同意才能生效。你提到"无需管理员同意即可查询"是错误的,只有委派权限(用户上下文)才可能不需要管理员同意,而客户端凭证流使用的是应用权限,必须完成管理员同意步骤:
- 回到Azure AD门户的应用注册页面,进入"API权限"
- 点击"授予管理员同意"按钮,完成权限激活
4. 正确调用API访问所有用户日历
客户端凭证流没有用户上下文,所以不能使用/me/calendars(这个端点需要当前登录用户的上下文)。要访问租户所有用户的日历,需要分两步:
步骤A:获取租户内所有用户列表
调用GET /users端点获取所有用户的ID或用户主体名称(UPN):
$users = $provider->get('users', $token); $userList = $users->getBody()->getContents(); // 解析JSON得到用户列表 $usersArray = json_decode($userList, true);
步骤B:遍历用户,获取每个用户的日历
对每个用户,调用GET /users/{user-id}/calendars或GET /users/{user-upn}/calendars端点:
$allCalendars = []; foreach ($usersArray['value'] as $user) { $userId = $user['id']; $calendarsResponse = $provider->get("users/$userId/calendars", $token); $calendars = json_decode($calendarsResponse->getBody()->getContents(), true); $allCalendars[$userId] = $calendars['value']; } // 输出所有用户的日历 echo json_encode($allCalendars);
批量请求优化(可选)
如果用户数量较多,逐个请求效率较低,可以使用Microsoft Graph的批量请求,一次性发送多个请求:
$batchRequest = [ 'requests' => [] ]; foreach ($usersArray['value'] as $index => $user) { $batchRequest['requests'][] = [ 'id' => (string)($index + 1), 'method' => 'GET', 'url' => "/users/{$user['id']}/calendars" ]; } $batchResponse = $provider->post('batch', $token, ['json' => $batchRequest]); $batchResult = json_decode($batchResponse->getBody()->getContents(), true);
5. 修改日历的示例
要修改某个用户的日历,调用PATCH /users/{user-id}/calendars/{calendar-id}端点:
$userId = '用户ID'; $calendarId = '日历ID'; $updateData = [ 'name' => '更新后的日历名称' ]; $updateResponse = $provider->patch("users/$userId/calendars/$calendarId", $token, ['json' => $updateData]); // 成功返回204 No Content echo $updateResponse->getStatusCode();
内容的提问来源于stack exchange,提问作者lowkey

