设置和配置 SharePoint Embedded
在本练习中,你将创建 Microsoft Entra ID 应用程序,在 Microsoft 365 SharePoint 租户上设置 SharePoint Embedded,并创建第一个容器类型和容器。
先决条件
- Postman 安装的 桌面客户端 登录到 Postman 免费或付费帐户。
- 访问 Microsoft 365 租户中的管理员帐户。
- Windows PowerShell 提示符。
注册 Microsoft Entra ID 应用程序
自定义 SharePoint Embedded 应用程序使用自定义 Microsoft Entra ID 应用程序进行身份验证并获取调用 Microsoft Graph 和 Microsoft SharePoint API 所需的权限。
打开浏览器并导航到 Microsoft Entra ID 管理员输入。 使用具有全局管理员权限的 Microsoft 365 租户 的工作或学校帐户 登录。
在左侧导航栏中选择“ 标识 > 应用程序 > ”“应用注册 ”,然后选择“ 新建注册”。
在 “注册应用程序” 页上,按如下所示设置值,然后选择 “注册”:
- 名称:SharePoint Embedded 应用
- 支持的帐户类型:任何组织目录中的帐户 (任何 Microsoft Entra ID 租户 - 多租户)
创建 SharePoint Embedded 应用程序后,Microsoft Entra ID 会显示新应用程序的详细信息。 创建一个文本文件,以跟踪稍后在本模块中需要的多个值。
将 应用程序 (客户端) ID & Directory (租户) ID 从应用的概述页复制到本地文本文件中。
配置身份验证
接下来,配置应用的身份验证设置。 从左侧导航栏中选择“ 管理 > 身份验证 ”,然后选择“ 添加平台”。
选择 “Web ”选项,对于 “重定向 URI”,输入 https://oauth.pstmn.io/v1/callback
并选择“ 配置”。
接下来,选择“为新的 Web 平台添加 URI”,https://oauth.pstmn.io/v1/browser-callback
然后选择“配置”。
本模块稍后将使用这两个选项通过 Postman 客户端进行身份验证和获取访问令牌。
向下滚动到 “隐式授权和混合流 ”部分,选择“ 访问令牌 (用于隐式流) ”选项,然后选择“ 保存”。
通过选择“添加平台单页应用程序”添加另一个平台,并将“重定向 URI”设置为 http://localhost
,然后选择“配置”。
配置 API 权限
接下来,使用更多权限配置应用,以便它可以创建和访问容器类型和容器。
注意
在发布时,必要的权限在 Microsoft Entra ID 管理中心的 API 选择器中尚不可见。 若要添加权限, FileStorageContainer.Selected
对于 Microsoft Graph 和 Container.Selected
for SharePoint,需通过权限 ID 直接将它们添加到应用程序的清单中。
从左侧导航栏中选择“ 管理 > 清单 ”。 找到 属性 requiredResourceAccess
并对其进行编辑,使其类似于以下 JSON:
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "4d114b1a-3649-4764-9dfb-be1e236ff371",
"type": "Scope"
},
{
"id": "19766c1b-905b-43af-8756-06526ab42875",
"type": "Role"
}
]
},
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "085ca537-6565-41c2-aca7-db852babc212",
"type": "Scope"
},
{
"id": "40dc41bc-0f7e-42ff-89bd-d9516947e474",
"type": "Role"
}
]
}
],
重要
不要使用相同的 创建重复对象 resourceAppIds
。 而是将其他对象添加到现有 resourceAppIds
。 例如,如果 requiredResourceAccess
已将 设置为以下内容:
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}
]
}
],
将两个新权限添加到现有 "resourceAppId": "00000003-0000-0000-c000-000000000000"
对象,使其现在具有 3 (3) 权限。
向管理员授予对新权限的同意
某些权限需要管理员同意。 在左侧导航栏中选择“ API 权限” ,滚动到页面底部,然后选择链接“ 企业应用程序”。
在“权限”页上,选择“ 授予 Contoso 管理员同意”。 如果系统提示登录,请使用用于登录 Microsoft Entra ID 管理中心的同一 工作和学校 帐户。 在“ 请求的权限” 页上,选择“ 接受 ”以向管理员授予两对权限的同意: FileStorageContainer.Selected
Microsoft Graph 和 Container.Selected
for SharePoint。 这两对表示应用程序 & 两个权限中每个权限的委托选项。
创建客户端密码
若要使应用使用具有 Microsoft Entra ID 的 OAuth2 客户端凭据流进行身份验证,它需要客户端 ID 和客户端密码。
从左侧导航栏中选择“ 管理 > 证书 & 机密 ”。
在 “客户端密码” 部分中,选择 “新建客户端密码”。 添加说明并选择过期持续时间,然后选择“添加”。
创建客户端密码后,会显示一次,因此请确保将其作为客户端密码复制到本地文本文件中,以供稍后在本模块中使用。 如果不复制此值,则将不得不创建新密码,因为永远无法查看以前创建的密码。
创建证书
与允许使用客户端 ID 和机密进行应用身份验证的 Microsoft Graph 不同,SharePoint 要求应用使用客户端 ID 和证书进行身份验证。 因此,我们现在需要创建一个证书。
以管理员身份打开 Windows PowerShell 提示符并运行以下 PowerShell 脚本。 出现提示时,输入证书的名称,例如 SharePoint Embedded:
$name = Read-Host -Prompt "Certificate name: "
$cert = New-SelfSignedCertificate -Subject "CN=$name" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256
Export-Certificate -Cert $cert -FilePath "$name.cer"
# Private key to Base64
$privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$privateKeyBytes = $privateKey.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
$privateKeyBase64 = [System.Convert]::ToBase64String($privateKeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)
# Private key file contents
$privateKeyFileContent = @"
-----BEGIN PRIVATE KEY-----
$privateKeyBase64
-----END PRIVATE KEY-----
"@
# Output to file
$privateKeyFileContent | Out-File -FilePath "$name.key" -Encoding Ascii
PowerShell 脚本会将证书 (*.cer
) 和私钥 (*.key
) 保存到执行 PowerShell 脚本的文件夹。
在 Microsoft Entra ID 管理中心,在应用的 “证书 & 机密 ”页上,选择“ 证书 ”,然后选择“ 上传证书”。
*.cer
上传 PowerShell 脚本生成的 ,为证书提供说明,然后选择“添加”。
上传证书后,将显示的整个 指纹 复制到本地文本文件。
为 SharePoint Embedded 应用创建新的容器类型
下一步是为应用程序创建容器类型。
以管理员身份打开 Windows PowerShell 提示符。
安装或升级 SharePoint Online PowerShell 模块
如果之前尚未安装 SharePoint Online PowerShell 模块,请通过执行以下命令进行安装:
Install-Module "Microsoft.Online.SharePoint.PowerShell"
如果已安装它,请确保通过升级安装了最新的一个:
Upgrade-Module "Microsoft.Online.SharePoint.PowerShell"
注意
如果不确定是否已安装,请尝试执行第 Upgrade-Module
一个。 如果失败,则不会安装它,因此请 Install-Module
运行 cmdlet。
创建容器类型
安装最新的 SharePoint Online PowerShell 模块 后,下一步是在租户中创建容器类型。
更新以下 PowerShell 脚本中的以下值,然后执行脚本:
-
{{SPO_ADMIN_URL}}
:这是 SharePoint Online 管理中心的 URL。 可以使用租户管理员帐户的工作和学校登录[https://portal.microsoft.com](https://portal.microsoft.com)
,选择左侧导航底部的“全部显示”,然后选择“管理中心 > SharePoint”来获取此权限。 复制 SharePoint 管理中心的 URL 并使用此值。 例如,如果租户 ID 为 Contoso123,则管理员 URL 将为https://contoso123-admin.sharepoint.com
。 -
{{CONTAINER_TYPE_NAME}}
:为新的容器类型选择一个名称。 例如,使用FirstContainerType
。 -
{{AZURE_ENTRA_APP_ID}}
: 将此设置为之前创建的 Microsoft Entra ID 应用 ID(也称为“客户端 ID”)的值。 此值应位于本地文本文件中。
Import-Module "Microsoft.Online.SharePoint.PowerShell"
Connect-SPOService -Url "{{SPO_ADMIN_URL}}"
New-SPOContainerType -TrialContainerType -ContainerTypeName "{{CONTAINER_TYPE_NAME}}" -OwningApplicationId "{{AZURE_ENTRA_APP_ID}}"
PowerShell 脚本将显示新容器类型的详细信息,例如:
Container Type ID:
===============================================================================
ContainerTypeId : 3a6b1fc4-0bd9-04b3-3a2a-4843fbb60914
ContainerTypeName : FirstContainerType
OwningApplicationId : 763cd5ea-ade4-4d2a-a143-29498920e18f
Classification : Standard
AzureSubscriptionId : 00000000-0000-0000-0000-000000000000
ResourceGroup :
Region :
将 ContainerTypeId
和 ContainerTypeName
复制到本地文本文件以供以后使用。
将 Postman 配置为使用 Microsoft Entra ID 进行身份验证,以获取 Microsoft Graph 和 SharePoint Online 的委托和应用程序访问令牌
最后一步是在开发人员/提供商 Microsoft 365 租户中创建的新容器类型注册到使用租户。 这是使用 SharePoint REST API 完成的。 对于此步骤,你将使用 Postman 客户端。
使用 Postman 调用 SharePoint REST API 之前,需要先将 Postman 配置为获取访问令牌。
创建新的 Postman 环境
首先,创建一个新的 Postman 环境,该环境将存储环境变量以简化调用并集中所有设置。
在 Postman 中,选择“ 环境”,然后选择加号图标以创建新环境。
将环境命名为 SharePoint Embedded。
将以下变量添加到环境中,并将 初始值 设置为本地文本文件中的 vales。 添加这些变量后,请保存环境。
变量 | 类型 | 注释 | 示例 |
---|---|---|---|
ClientID | 默认 | 以前在 Microsoft Entra ID 管理中心中创建的应用的客户端/应用程序 ID。 | 763cd5ea-ade4-4d2a-a143-29498920e18f |
ClientSecret | 秘密 | 以前在 Microsoft Entra ID 管理中心中创建的应用的客户端密码。 | JXZ8Q........jbvanC |
ConsumingTenantId | 默认 | 租户/目录 ID (以前在 Microsoft Entra ID 管理中心中创建的应用的 GUID) 。 | 4c57ca2e-a63d-4999-9b69-610a7296e89b |
RootSiteURL | 默认 | SharePoint Online 租户的 URL,不带尾部斜杠。 这与没有 -admin 字符串的 SharePoint Online 管理中心 URL 相同。 | https://contoso123.sharepoint.com |
ContainerTypeId | 默认 | 之前创建的容器类型的 ID | 3a6b1fc4-0bd9-04b3-3a2a-4843fbb60914 |
TenantName | 默认 | SharePoint Online 租户的 ID。 这是没有 -admin 字符串的 SharePoint Online 管理中心 URL 的第一部分。 | contoso123 |
CertThumbprint | 默认 | 之前为在 Microsoft Entra ID 管理中心中创建的应用上传的证书的指纹。 | 905EEA21C472368A36ADEDB26CCE6E760049BC1E |
CertPrivateKey | 秘密 | 证书私钥。 从先前执行的 PowerShell 脚本生成的 *.key 文件中复制全部内容,包括证书的-----begin 和结束分隔符。 | ----BEGIN 私钥----- ... -----END 私钥---— |
ContainerID | 默认 | 将其留空。 稍后会使用它。 |
通过在 Postman 客户端右上角的下拉选择器中选择环境来选择环境。 下拉列表位于齿轮和警报图标下方,以及选项卡所在的同一行上的 “保存 和 共享 ”按钮上方。
创建新的 Postman 集合
接下来,创建一个新的 Postman 集合,用于存储请求并获取访问令牌。
在 Postman 中,选择“ 集合”,然后选择加号图标以创建新集合
将集合命名为 SharePoint Embedded。
在新集合中,使用集合的上下文创建到文件夹集,以存储 对容器的请求;一个在 委托 文件夹中,一个在 应用程序 文件夹中:
配置 Postman 集合的 Application 文件夹
下一步是更新 Application 文件夹的身份验证设置。 这需要更多工作,因为我们需要获取仅限应用的令牌才能调用 Microsoft Graph。 为此,需要配置 Application 文件夹以下载脚本,并将其存储在全局变量中供以后使用。
选择“ 应用程序 ”文件夹,然后选择“ 授权 ”选项卡。将 “类型 ”设置为 “持有者令牌 ”,并将 “令牌 ”设置为 {{AppOnlyCertGraphToken}}
。
注意
Postman 将显示验证错误,如下所示。 这是意料之中的,暂时可以忽略。 稍后将使用脚本创建此变量。
选择“ 请求前脚本 ”选项卡,然后输入以下脚本:
// download jsrsasign library and save it to a global variable
if (!pm.globals.has('jsrsasign-js')) {
pm.sendRequest(
'https://kjur.github.io/jsrsasign/jsrsasign-all-min.js',
function (err, res) {
if (err) {
throw new Error(err);
} else {
console.log('Downloaded RSA library');
pm.globals.set('jsrsasign-js', res.text());
}
}
);
}
保存对集合所做的更改。
配置 Postman 集合的“应用程序 >容器” 文件夹
接下来,选择集合的 “应用程序 > 容器” 文件夹。
在“ 授权 ”选项卡上,将“ 类型 ”设置为 “继承父级的身份验证”。
在“ 请求前脚本 ”选项卡上,输入以下脚本:
async function ensureAccessToken () {
var validToken = false;
var token = pm.environment.get('AppOnlyCertGraphToken');
if (token) {
console.log('checking stored token');
try {
var tokenObj = KJUR.jws.JWS.parse(token);
var nbf = tokenObj.payloadObj.nbf;
var exp = tokenObj.payloadObj.exp;
var now = getTimeInSec();
if (nbf <= now && now < exp) {
validToken = true;
} else {
console.log("Stored access token is expired");
}
} catch (e) {
console.log("Unable to parse stored access token");
}
} else {
console.log("No access token found");
}
if (!validToken) {
acquireAccessToken();
}
}
function acquireAccessToken() {
console.log("Acquiring a new access token");
var jwt = getRequestJwt();
console.log(jwt);
var tid = pm.environment.get('ConsumingTenantId');
const tokenRequest = {
url: `https://login.microsoftonline.com/${tid}/oauth2/v2.0/token`,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'client_assertion_type', value: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' },
{ key: 'client_assertion', value: jwt },
{ key: 'client_id', value: pm.environment.get('ClientID') },
{ key: 'scope', value: 'https://graph.microsoft.com/.default' },
{ key: 'grant_type', value: 'client_credentials' }
]
}
};
pm.sendRequest(tokenRequest, (error, response) => {
if (error) {
console.log('Unable to acquire token: ' + error);
} else {
var responseJson = response.json();
var token = responseJson.access_token;
if (!token)
throw Error("Invalid or no access token received");
pm.environment.set('AppOnlyCertGraphToken', token);
}
});
}
function getRequestJwt () {
var header = {
'alg': 'RS256',
'typ': 'JWT',
'x5t': safeBase64EncodedThumbprint(pm.environment.get('CertThumbprint'))
};
var now = getTimeInSec();
var tid = pm.environment.get('ConsumingTenantId');
var payload = {
'aud': `https://login.microsoftonline.com/${tid}/oauth2/v2.0/token`,
'exp': now + 60 * 60,
'iss': pm.environment.get('ClientID'),
'jti': pm.variables.replaceIn('{{$guid}}'),
'nbf': now,
'sub': pm.environment.get('ClientID'),
'iat': now
};
var encryptedPk = pm.environment.get('CertPrivateKey');
var decryptedPk = encryptedPk;
if (pm.environment.has('CertPassword') && pm.environment.get('CertPassword') !== '') {
decryptedPk = KEYUTIL.getKey(encryptedPk, pm.environment.get('CertPassword'));
}
var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(payload);
return KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, decryptedPk);
}
function getTimeInSec() {
return Math.floor(Date.now() / 1000);
}
function safeBase64EncodedThumbprint (thumbprint) {
var numCharIn128BitHexString = 128/8*2;
var numCharIn160BitHexString = 160/8*2;
var thumbprintSizes = {};
thumbprintSizes[numCharIn128BitHexString] = true;
thumbprintSizes[numCharIn160BitHexString] = true;
var thumbprintRegExp = /^[a-f\d]*$/;
var hexString = thumbprint.toLowerCase().replace(/:/g, '').replace(/ /g, '');
if (!thumbprintSizes[hexString.length] || !thumbprintRegExp.test(hexString)) {
throw 'The thumbprint does not match a known format';
}
var base64 = (Buffer.from(hexString, 'hex')).toString('base64');
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
var navigator = {}; // fake a navigator object for the lib
var window = {}; // fake a window object for the lib
eval(pm.globals.get('jsrsasign-js'));
ensureAccessToken();
注意
此脚本将使用环境变量通过 Microsoft Entra ID 进行身份验证,以获取 Microsoft Graph 的仅限应用的令牌,并将其存储在名为 AppOnlyCertGraphToken
的新变量或现有变量中。 必须使用脚本完成此操作,因为 Postman 不支持使用客户端证书而不是客户端密码的 OAuth2 客户端凭据流。
现在,从 应用程序 > 容器 文件夹运行请求时,请求的预脚本将获取令牌,更新在父 应用程序 文件夹的“授权”选项卡中设置的环境变量,然后执行请求。 由于请求位于 “容器 ”文件夹中,该文件夹的 “授权 ”选项卡设置为从父级继承,因此它将拾取重置的身份验证配置。
保存对集合所做的更改。
此时,Postman 现已配置为获取仅限应用的访问令牌,用于调用 Microsoft Graph。
向使用租户注册容器类型
配置 Postman 后,现在可以添加请求,将容器类型注册到使用租户。
在 Postman 中,从左侧导航栏中选择“集合”,展开“SharePoint Embedded > 应用程序”节点,选择“容器”节点上的“...”上下文菜单,然后选择“添加请求”。
将请求重命名为 “注册容器类型”。
将 HTTP 方法设置为 PUT 和以下终结点:
{{RootSiteURL}}/_api/v2.1/storageContainerTypes/{{ContainerTypeId}}/applicationPermissions
在 “预请求脚本”上,添加以下代码以获取仅限应用的令牌以调用 SharePoint REST API:
async function ensureAccessToken () {
var validToken = false;
var token = pm.environment.get('AppOnlyCertSPOToken');
if (token) {
console.log('checking stored token');
try {
var tokenObj = KJUR.jws.JWS.parse(token);
var nbf = tokenObj.payloadObj.nbf;
var exp = tokenObj.payloadObj.exp;
var now = getTimeInSec();
if (nbf <= now && now < exp) {
validToken = true;
} else {
console.log("Stored access token is expired");
}
} catch (e) {
console.log("Unable to parse stored access token");
}
} else {
console.log("No access token found");
}
if (!validToken) {
acquireAccessToken();
}
}
function acquireAccessToken() {
console.log("Acquiring a new access token");
var jwt = getRequestJwt();
console.log(jwt);
var tid = pm.environment.get('ConsumingTenantId');
const tokenRequest = {
url: `https://login.microsoftonline.com/${tid}/oauth2/v2.0/token`,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'client_assertion_type', value: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' },
{ key: 'client_assertion', value: jwt },
{ key: 'client_id', value: pm.environment.get('ClientID') },
{ key: 'scope', value: pm.environment.get('RootSiteURL') + '/.default' },
{ key: 'grant_type', value: 'client_credentials' }
]
}
};
pm.sendRequest(tokenRequest, (error, response) => {
if (error) {
console.log('Unable to acquire token: ' + error);
} else {
var responseJson = response.json();
var token = responseJson.access_token;
pm.environment.set('AppOnlyCertSPOToken', token);
}
});
}
function getRequestJwt () {
var header = {
'alg': 'RS256',
'typ': 'JWT',
'x5t': safeBase64EncodedThumbprint(pm.environment.get('CertThumbprint'))
};
var now = getTimeInSec();
var tid = pm.environment.get('ConsumingTenantId');
var payload = {
'aud': `https://login.microsoftonline.com/${tid}/oauth2/v2.0/token`,
'exp': now + 60 * 60,
'iss': pm.environment.get('ClientID'),
'jti': pm.variables.replaceIn('{{$guid}}'),
'nbf': now,
'sub': pm.environment.get('ClientID'),
'iat': now
};
var encryptedPk = pm.environment.get('CertPrivateKey');
var decryptedPk = encryptedPk;
if (pm.environment.has('CertPassword') && pm.environment.get('CertPassword') !== '') {
decryptedPk = KEYUTIL.getKey(encryptedPk, pm.environment.get('CertPassword'));
}
var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(payload);
return KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, decryptedPk);
}
function getTimeInSec() {
return Math.floor(Date.now() / 1000);
}
function safeBase64EncodedThumbprint (thumbprint) {
var numCharIn128BitHexString = 128/8*2;
var numCharIn160BitHexString = 160/8*2;
var thumbprintSizes = {};
thumbprintSizes[numCharIn128BitHexString] = true;
thumbprintSizes[numCharIn160BitHexString] = true;
var thumbprintRegExp = /^[a-f\d]*$/;
var hexString = thumbprint.toLowerCase().replace(/:/g, '').replace(/ /g, '');
if (!thumbprintSizes[hexString.length] || !thumbprintRegExp.test(hexString)) {
throw 'The thumbprint does not match a known format';
}
var base64 = (Buffer.from(hexString, 'hex')).toString('base64');
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
var navigator = {}; // fake a navigator object for the lib
var window = {}; // fake a window object for the lib
eval(pm.globals.get('jsrsasign-js'));
ensureAccessToken();
在“ 授权 ”选项卡上,将 “类型 ”设置为“ 持有者令牌 ”,并将“ 令牌 ”设置为 {{AppOnlyCertSPOToken}}
。 此令牌变量是在请求前脚本中创建和设置的。
在“ 正文 ”选项卡上,选择 原始 类型,将数据类型设置为 JSON ,并将以下代码添加到正文:
{
"value": [
{
"appId": "{{ClientID}}",
"delegated": ["full"],
"appOnly": ["full"]
}
]
}
选择“ 发送 ”按钮以执行 “注册容器类型” 请求。 Postman 将在请求下方显示响应:
摘要
在本练习中,你创建了 Microsoft Entra ID 应用程序,在 Microsoft 365 SharePoint 租户上设置 SharePoint Embedded,并使用 PowerShell 创建第一个容器类型、一个新的 Postman 集合和关联的环境,用于向 Microsoft Graph 和 SharePoint REST API 提交请求。