本文内容
Copilot Studio 支持单一登录 (SSO)。 SSO允许网站上的助手为已经登录已部署助手的页面或应用程序的客户进行登录。
例如,助手托管在用户已登录的企业 Intranet 中或应用中。
为 Copilot Studio 配置 SSO 主要分四个步骤:
在 Microsoft Entra ID 中为您的自定义画布创建一个应用程序注册。
要为助手定义自定义范围:
在 Copilot Studio 中配置身份验证以启用 SSO。
配置您的自定义区域 HTML 代码以启用 SSO。
先决条件
支持的渠道
下表详细介绍了当前支持 SSO 的渠道 。 您可以在 Microsoft Copilot Studio 创意论坛 中建议支持其他渠道。
1 如果您还启用了 Teams 渠道,您需要遵循在 Microsoft Teams 中的使用 Microsoft Entra ID 为助手配置单点登录 文档上的配置说明。 未能按照该页面上的说明配置 Teams SSO 设置,会致使您的用户在使用 Teams 渠道时始终无法通过身份验证。
2 仅支持实时聊天渠道。 有关详细信息,请参阅配置转接到 Dynamics 365 Customer Service 。
重要提示
当助手出现以下任一情况时,目前不支持 SSO:
但是,作为 SPFx 组件 发布到 SharePoint 网站的助手支持SSO。
为自定义网站创建应用注册
要启用 SSO,您需要创建两个单独的应用注册:
身份验证应用程序注册 ,为您的助手启用 Microsoft Entra ID 用户身份验证
一个画布应用注册 ,它支持自定义网页的 SSO
出于安全原因,我们不建议在您的助手和自定义网站上重复使用相同的应用程序注册。
按照使用 Microsoft Entra ID 配置用户身份验证 中的说明创建身份验证应用程序注册。
再次按照说明创建身份验证应用注册,创建第二个应用注册,作为您的画布应用注册。
将画布应用程序注册 ID 添加到身份验证应用程序注册中。
添加令牌交换 URL
若要更新 Copilot Studio 中的 Microsoft Entra ID 身份验证设置,您需要添加令牌交换 URL 以允许您的应用程序和 Copilot Studio 共享信息。
在身份验证应用注册边栏选项卡上的 Azure 门户中,转到公开 API 。
在范围 下,选择复制到剪贴板 图标。
在 Copilot Studio 内的导航菜单中的设置 下面,选择安全性 ,然后选择身份验证 磁贴。
对于令牌交换 URL(SSO 需要) ,粘贴之前复制的范围。
选择保存 。
创建画布应用注册后,转到身份验证 ,然后选择添加平台 。
在平台配置 下,选择添加平台 ,然后选择 Web 。
在重定向 URL 下,输入您的网页的 URL,例如 http://contoso.com/index.html
。
在隐式授权和混合流 部分中,打开访问令牌(用于隐式流) 和 ID 令牌(用于隐式和混合流) 。
选择配置 。
查找您的助手令牌终结点 URL
在 Copilot Studio 中,打开助手,然后选择渠道 。
选择移动应用 。
在令牌终结点 下面,选择复制 。
在网页中配置 SSO
使用 Copilot Studio 的 GitHub 存储库 中提供的代码为重定向 URL 创建网页。 从 GitHub 存储库复制代码,然后按照以下说明进行修改。
备注
GitHub 存储库中的代码要求用户选择一个登录按钮或从不同的站点登录。 要启用自动登录,请将以下代码添加到 aysnc function main()
的开头:
(async function main() {
if (clientApplication.getAccount() == null) {
await clientApplication.loginPopup(requestObj).then(onSignin).catch(function (error) {console.log(error) });
}
// Add your BOT ID below
var theURL =
转到 Azure 门户内的概览 页面,从画布应用注册中复制应用程序(客户端)ID 和目录(租户)ID 。
要配置 Microsoft 身份验证库 (MSAL):
将 clientId
分配给您的应用程序(客户端)ID 。
将 authority
分配给 https://login.microsoftonline.com/
,将您的目录(租户)ID 添加到末尾。
例如:
var clientApplication;
(function (){
var msalConfig = {
auth: {
clientId: '00001111-aaaa-2222-bbbb-3333cccc4444',
authority: 'https://login.microsoftonline.com/7ef988bf-xxxx-51af-01ab-2d7fd011db47'
},
将 theURL
变量设置为以前复制的令牌终结点 URL。 例如:
(async function main() {
var theURL = "https://<token endpoint URL>"
编辑 userId
的值以包含自定义前缀。 例如:
var userId = clientApplication.account?.accountIdentifier != null ?
("My-custom-prefix" + clientApplication.account.accountIdentifier).substr(0, 64)
: (Math.random().toString() + Date.now().toString()).substr(0,64);
保存您的更改。
使用您的网页测试您的助手
在浏览器中打开网页。
选择登录 。
备注
如果您的浏览器阻止弹出窗口,或者您正在使用匿名或隐私浏览窗口,系统会提示您登录。 否则,使用验证码完成登录。
新的浏览器选项卡将打开。
切换到新标签页,复制验证码。
使用您的助手切换回该选项卡,并将验证码粘贴到助手对话中。
相关内容
技术概述
下图显示 Copilot Studio 中如何在不显示登录提示的情况下让用户登录 (SSO):
助手用户输入用于触发登录主题 的短语。 此登录主题旨在使用户登录并使用用户的已验证令牌(User.AccessToken
变量) 。
Copilot Studio 发送登录提示以允许用户使用为其配置的标识提供者登录。
助手的自定义区域 会截获登录提示,并请求 Microsoft Entra ID 提供一个代理 (OBO) 令牌。 区域将该令牌发给助手。
在收到 OBO 令牌时,助手会用此 OBO 令牌换回一个“访问令牌”,并使用此访问令牌的值填写 AuthToken
变量。 此时还将设置 IsLoggedIn
变量。
在 Microsoft Entra ID 中为您的自定义画布创建一个应用程序注册
要启用 SSO,您需要两个单独的应用注册:
重要提示
您不能为助手的用户身份验证和自定义画布重复使用相同的应用注册。
为助手的区域创建应用注册
登录到 Azure 门户 。
通过选择图标或在顶部搜索栏中进行搜索,转到应用注册 。
选择新建注册 。
输入该注册的名称。 如果使用您在注册的区域所属助手的名称,并且加入“canvas”来帮助与用于进行身份验证的语言支持区分开来,可能非常有用。
例如,如果助手的名称为“Contoso sales help”,可以将应用注册命名为“ContosoSalesCanvas”或类似名称。
在支持的帐户类型 下,选择任何组织租户中的帐户(任何 Microsoft Entra ID 目录 - 多租户)和个人 Microsoft 帐户(例如 Skype、Xbox) 。
暂时将重定向 URI 部分保留为空,因为后面的步骤中将输入该信息。 选择注册 。
注册完成后,将在概述 页中打开。 转到清单 。 确认 accessTokenAcceptedVersion
设置为 2
。 如果未设置,将其更改为 2
,然后选择保存 。
添加重定向 URL
打开注册后,转到身份验证 ,然后选择添加平台 。
在配置平台 边栏选项卡中,选择 Web 。
在重定向 URI 下,添加托管聊天区域的页面的完整 URL。 在隐式授予 部分下,选中 ID 令牌 和访问令牌 复选框。
选择配置 以确认更改。
转到 API 权限 。 选择为 <您的租户名称> 授予管理员同意 ,然后选择是 。
重要提示
若要避免用户必须同意每个应用程序,必须由全局管理员、应用程序管理员或云应用程序管理员为您的应用注册授予租户范围的同意 。
为您的助手定义自定义范围
通过在身份验证应用注册内为区域应用注册公开 API 来定义自定义范围。 范围 用于确定用户和管理员的角色和访问权限。
此步骤在用于进行身份验证的身份验证应用注册与自定义区域的应用注册之间建立信任关系。
打开在配置身份验证时 创建的应用注册。
转至 API 权限 ,并确保为助手添加正确的权限。 选择为 <您的租户名称> 授予管理员同意 ,然后选择是 。
重要提示
若要避免用户必须同意每个应用程序,必须由全局管理员、应用程序管理员或云应用程序管理员为您的应用注册授予租户范围的同意 。
转到公开 API ,然后选择添加范围 。
为范围输入名称,以及用户进入 SSO 屏幕时应显示给他们的显示信息。 选择添加范围 。
选择添加客户端应用程序 。
在客户端 ID 字段中输入来自区域应用注册的概述 页的应用程序(客户端)ID 。 选中创建并列出的范围的复选框。
选择添加应用程序 。
Copilot Studio 身份验证配置页中的令牌交换 URL 用于以 OBO 令牌交换通过 Bot Framework 请求的访问令牌。
Copilot Studio 调用 Microsoft Entra ID 来执行实际交换。
登录到 Copilot Studio。
通过选择顶部菜单中的助手图标,然后选择正确助手,确认已经选择了要为其启用身份验证的助手。
在导航菜单中的设置 下面,选择安全性 。 然后,选择身份验证 卡片。
从助手的身份验证应用注册的公开 API 边栏选项卡,在令牌交换 URL 字段中输入完整的范围 URI。 URI 格式为 api://1234-4567/scope.name
。
选择保存 ,然后发布助手内容。
更新助手所在自定义区域页以拦截登录卡请求和交换 OBO 令牌。
通过在 <head> 部分的 <script> 标记中添加以下代码,配置 Microsoft 身份验证库 (MSAL)。
使用区域应用注册的应用程序(客户端)ID 更新 clientId
。 将 <Directory ID>
替换为目录(租户)ID 。 可从区域应用注册的概述 页获取这些 ID。
<head>
<script>
var clientApplication;
(function () {
var msalConfig = {
auth: {
clientId: '<Client ID [CanvasClientId]>',
authority: 'https://login.microsoftonline.com/<Directory ID>'
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
}
};
if (!clientApplication) {
clientApplication = new Msal.UserAgentApplication(msalConfig);
}
} ());
</script>
</head>
在 <body> 中插入以下 <脚本>。 此脚本调用方法以检索 resourceUrl
,并用当前令牌换取 OAuth 提示请求的令牌。
<script>
function getOAuthCardResourceUri(activity) {
if (activity &&
activity.attachments &&
activity.attachments[0] &&
activity.attachments[0].contentType === 'application/vnd.microsoft.card.oauth' &&
activity.attachments[0].content.tokenExchangeResource) {
// asking for token exchange with Microsoft Entra ID
return activity.attachments[0].content.tokenExchangeResource.uri;
}
}
function exchangeTokenAsync(resourceUri) {
let user = clientApplication.getAccount();
if (user) {
let requestObj = {
scopes: [resourceUri]
};
return clientApplication.acquireTokenSilent(requestObj)
.then(function (tokenResponse) {
return tokenResponse.accessToken;
})
.catch(function (error) {
console.log(error);
});
}
else {
return Promise.resolve(null);
}
}
</script>
在 <body> 中插入以下 <脚本>。 在该 main
方法中,此代码会向 store
添加一个条件,以及您的助手唯一标识符。 它还会生成一个唯一 ID 作为 userId
变量。
使用助手的 ID 更新 <COPILOT ID>
。 可以查看助手的 ID,方法是转到正在使用的助手的渠道 选项卡,然后选择 Copilot Studio 门户中的移动应用 。
<script>
(async function main() {
// Add your COPILOT ID below
var BOT_ID = "<BOT ID>";
var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;
const {
token
} = await fetchJSON(theURL);
var directline = await fetchJSON(regionalChannelSettingsURL).then(res=> res.channelUrlsById.directline);
const directLine = window.WebChat.createDirectLine({
domain: `${directline}v3/directline`,
token
});
var userID = clientApplication.account?.accountIdentifier != null ?
("Your-customized-prefix-max-20-characters" + clientApplication.account.accountIdentifier).substr(0, 64) :
(Math.random().toString() + Date.now().toString()).substr(0, 64); // Make sure this will not exceed 64 characters
const store = WebChat.createStore({}, ({
dispatch
}) => next => action => {
const {
type
} = action;
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'startConversation',
type: 'event',
value: {
text: "hello"
}
}
});
return next(action);
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const activity = action.payload.activity;
let resourceUri;
if (activity.from && activity.from.role === 'bot' &&
(resourceUri = getOAuthCardResourceUri(activity))) {
exchangeTokenAsync(resourceUri).then(function(token) {
if (token) {
directLine.postActivity({
type: 'invoke',
name: 'signin/tokenExchange',
value: {
id: activity.attachments[0].content.tokenExchangeResource.id,
connectionName: activity.attachments[0].content.connectionName,
token,
},
"from": {
id: userID,
name: clientApplication.account.name,
role: "user"
}
}).subscribe(
id => {
if (id === 'retry') {
// copilot was not able to handle the invoke, so display the oauthCard
return next(action);
}
// else: tokenexchange successful and we do not display the oauthCard
},
error => {
// an error occurred to display the oauthCard
return next(action);
}
);
return;
} else
return next(action);
});
} else
return next(action);
} else
return next(action);
});
const styleOptions = {
// Add styleOptions to customize Web Chat canvas
hideUploadButton: true
};
window.WebChat.renderWebChat({
directLine: directLine,
store,
userID: userID,
styleOptions
},
document.getElementById('webchat')
);
})().catch(err => console.error("An error occurred: " + err));
</script>
完整示例代码
有关更多信息,可使用 MSAL 查找完整的示例代码,并存储我们的 GitHub 存储库 中已经包含的条件脚本。