Copilot Studio 支持单一登录 (SSO)。 SSO 允许您网站上的代理在客户已登录到部署了代理的页面或应用时登录。
例如,代理托管在用户已登录的企业 Intranet 中或应用中。
为 Copilot Studio 配置 SSO 有五个主要步骤:
使用 Microsoft Entra ID 为代理启用手动身份验证
在 Microsoft Entra ID 中为您的自定义画布创建一个应用程序注册。
在 Microsoft Entra ID 中为您的代理定义自定义范围。
将自定义范围添加到代理配置。
配置自定义画布客户端代码以启用 SSO。
先决条件
支持的渠道
下表详细介绍了当前支持 SSO 的渠道。 您可以在 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 配置用户身份验证中的说明创建身份验证应用程序注册。
创建第二个应用注册,这将用作您的画布应用注册。
添加令牌交换 URL
若要更新 Copilot Studio 中的 Microsoft Entra ID 身份验证设置,您需要添加令牌交换 URL 以允许您的应用程序和 Copilot Studio 共享信息。
在身份验证应用注册边栏选项卡上的 Azure 门户中,转到公开 API。
在范围下,选择复制到剪贴板图标。
在 Copilot Studio 内的导航菜单中的设置下面,选择安全性,然后选择身份验证磁贴。
对于令牌交换 URL(SSO 需要),粘贴之前复制的范围。
选择保存。
创建画布应用注册后,转到身份验证,然后选择添加平台。
在平台配置下,选择添加平台,然后选择 SPA。
在重定向 URL 下,输入您的网页的 URL,例如 http://contoso.com/index.html
。
在隐式授权和混合流部分中,打开访问令牌(用于隐式流)和 ID 令牌(用于隐式和混合流)。
选择配置。
查找 代理 的令牌终结点 URL
在 Copilot Studio 中,打开代理,然后选择渠道。
选择移动应用。
在令牌终结点下面,选择复制。
在网页中配置 SSO
使用 Copilot Studio 的 GitHub 存储库中提供的代码为重定向 URL 创建网页。 从 GitHub 存储库复制代码,然后按照以下说明进行修改。
转到 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/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
},
将 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);
保存您的更改。
验证是否已成功配置 SSO。
测试代理时,如果 SSO 没有成功配置,系统会提示您登录,并给出一个验证码,您必须将其复制到聊天窗口。
如果您看到登录提示,请验证是否正确完成了此过程的第 1 步到第 5 步。 如果 SSO 有成功配置,则不会提示登录。
备注
GitHub 存储库中的代码要求用户单击登录按钮。 在生产环境中,您可能希望将按钮功能替换为更合适的事件,例如导航到页面
相关内容
技术概述
下图显示 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 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 存储库中已经包含的条件脚本。