本文内容
Copilot Studio 支持单一登录 (SSO)。 SSO 允许您网站上的代理在客户已经登录到部署了代理的页面或应用程序时登录。
例如,代理托管在公司 Intranet 或用户已登录的应用程序中。
为 Copilot Studio 配置 SSO 主要分四个步骤:
在 Microsoft Entra ID 中为您的自定义画布创建一个应用程序注册。
为您的代理定义自定义范围。
在 Copilot Studio 中配置身份验证以启用 SSO。
配置您的自定义区域 HTML 代码以启用 SSO。
先决条件
支持的渠道
下表详细介绍了当前支持 SSO 的渠道 。 您可以在想法论坛上 建议支持额外的频道 Copilot Studio 。
1 如果您还启用了 Teams 通道,则需要跟随文档中的使用 ID 为代理 Microsoft Entra 配置单点登录中的配置说明 Microsoft Teams 。 未能按照该页面上的说明配置 Teams SSO 设置,会致使您的用户在使用 Teams 渠道时始终无法通过身份验证。
2 仅支持实时聊天渠道。 有关详细信息,请参阅配置转接到 Dynamics 365 Customer Service 。
重要提示
当代理已满足以下任一条件时,当前不支持 SSO:
但是,已作为 SharePoint SPFx 组件 发布到 网站的代理支持 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 发送登录提示以允许用户使用为其配置的标识提供者登录。
代理的 自定义画布 会拦截登录提示,并从 ID 请求代表(OBO)令牌 Microsoft Entra 。 画布将令牌发送到代理。
收到 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。
通过选择顶部菜单上的代理图标并选择正确的代理,确认您已选择要为其启用身份验证的代理。
在导航菜单中的设置 下面,选择安全性 。 然后,选择身份验证 卡片。
在“令牌交换 URL ” 字段中输入代理的身份验证应用注册的“公开 API ” 边栏选项卡中的完整范围 URI。 URI 格式为 api://1234-4567/scope.name
。
选择 保存 并发布代理内容。
更新代理所在的自定义画布页面,拦截 login 卡片请求并兑换 OBO Token。
通过在 <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
,此代码向 your store
添加一个条件,其中包含代理的唯一标识符。 它还会生成一个唯一 ID 作为 userId
变量。
使用您的代理的 ID 进行更新 <COPILOT ID>
。 您可以通过转到 您正在使用的代理的 Channels(频道)选项卡 ,然后在门户上 选择 Mobile app Copilot Studio (移动应用程序)来查看您的代理的 ID。
<script>
(async function main() {
// Add your AGENT 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') {
// The agent 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 存储库 中已经包含的条件脚本。