如何为网站上部署的聊天机器人配置 SSO [英] How to configure SSO for chatbot deployed on website

查看:26
本文介绍了如何为网站上部署的聊天机器人配置 SSO的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用以下代码配置 SSO.

 <!DOCTYPE html><html><头><title>Contoso 示例 Web 聊天</title><script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script><script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.0/js/msal.js"></script><script src="https://unpkg.com/@azure/storage-blob@10.3.0/browser/azure-storage.blob.min.js"完整性="sha384-fsfhtLyVQo3L3Bh73qgQoRR328xEeXnRGdoi53kjo1uectCfAHFfavrBBN2Nkbdf"crossorigin="匿名"></脚本><脚本类型="文本/javascript">if (typeof Msal === 'undefined') document.write(unescape("%3Cscript src='https://alcdn.msauth.net/lib/1.2.0/js/msal.js' type='text/javascript' %3E%3C/script%3E"));</脚本><风格>html,身体 {高度:100%;}身体 {边距:0;}.modal {显示:无;/* 默认隐藏 */位置:固定;/* 原地不动 */z-index:1;/* 坐在上面 */填充顶部:100px;/* 盒子的位置 */左:0;顶部:0;宽度:100%;/* 全屏宽度 */高度:100%;/* 全高 */溢出:自动;/* 如果需要,启用滚动 */背景颜色:rgb(0, 0, 0);/* 后备颜色 */背景颜色:rgba(0, 0, 0, 0.4);/* 黑色带不透明度 */}.modal-内容{背景颜色:#fefefe;边距:自动;填充:10px;边框:1px 实心 #888;宽度:500px;高度:575px;}.关闭 {颜色:黑色;浮动:对;字体大小:28px;字体粗细:粗体;}.close:悬停,.close:焦点{颜色:#000;文字装饰:无;光标:指针;}.主要的 {边距:18px;边框半径:4px;}div[角色=表格"] {背景颜色:#3392ff;}#网络聊天{位置:中心;高度:530px;宽度:100%;顶部:60px;溢出:隐藏;}#标题{底部填充:5px;}h1 {字体大小:14px;字体系列:Segoe UI;字体样式:正常;字体粗细:600;字体大小:14px;行高:20px;颜色:#f3f2f1;字母间距:0.005em;显示:表格单元格;垂直对齐:中间;填充:13px 0px 0px 20px;}/*#聊天窗口{高度:530px;宽度:100%;溢出:隐藏;顶部:60px;位置:中心;}*/#登录 {位置:固定;左边距:150px;}.跨度 {字体粗细:粗体;}#myBtn {位置:固定;浮动:对;大纲:无;宽度:60px;高度:80px;边距:自动自动自动 10px;}按钮:悬停{背景颜色:透明;}</风格></头><身体><button id="myBtn" type="button">Power 虚拟代理</button><div id="myModal" class="modal"><div class="modal-content" style="background-color: #ffd933"><span class="close">&times;</span><div id="聊天窗口"><div id="标题"><div><span>SSO 测试机器人</span></div></div><!-- <div style="z-index: 100;position: absolute;margin-top: 50px;width: 100%;">

<label id="userName" name="userName" style="width:75%;height:15px;border-color: Transparent;">未登录.</label><button id="login" name="login" onclick="onSignInClick()">登录</button></div></div>--><div id="webchat"></div></div></div></div><脚本>//按钮代码从这里开始//获取模态var modal = document.getElementById("myModal");//获取打开modal的按钮var btn = document.getElementById("myBtn");//获取 <span>关闭模态的元素var span = document.getElementsByClassName("close")[0];//当用户点击按钮时,打开 modalbtn.onclick = 函数 () {modal.style.display = "块";};//当用户点击<span>(x)、关闭模态span.onclick = function () {modal.style.display = "无";};//当用户点击模态框外的任意位置时,关闭它window.onclick = 函数(事件){if (event.target == modal) {modal.style.display = "无";}};//按钮代码到此结束</脚本><脚本>函数 onSignin(idToken){alert("KMT - 内部 onSignin:" + idToken);让用户 = clientApplication.getAccount();alert("KMT - 用户名:" + 用户名);document.getElementById("userName").innerHTML = "当前登录为 " + user.name;让 requestObj1 = {范围:["user.read", 'openid', 'profile']};}函数 onSignInClick(){//console.log("内部 onSignInClick");让 requestObj = {范围:["user.read", 'openid', 'profile']};clientApplication.loginPopup(requestObj).then(onSignin).catch(function (error) {console.log(error) });}函数getOAuthCardResourceUri(活动){if (activity && activity.attachments && activity.attachments[0] &&activity.attachments[0].contentType === 'application/vnd.microsoft.card.oauth' &&activity.attachments[0].content.tokenExchangeResource) {//请求与 AAD 交换代币返回activity.attachments[0].content.tokenExchangeResource.uri;}}函数 exchangeTokenAsync(resourceUri) {让用户 = clientApplication.getAccount();如果(用户){让 requestObj = {范围:["user.read", 'openid', 'profile']};return clientApplication.acquireTokenSilent(requestObj).then(function (tokenResponse) {返回 tokenResponse.accessToken;}).catch(函数(错误){控制台日志(错误);});}别的 {返回 Promise.resolve(null);}}异步函数 fetchJSON(url, options = {}) {const res = await fetch(url, {...选项,标题:{...options.headers,接受:'应用程序/json'}});如果 (!res.ok){throw new Error(`由于 ${res.status} 无法获取 JSON`);}返回等待 res.json();}</脚本><脚本>var 客户端应用程序;(功能 (){var msalConfig = {授权:{clientId: '7dd5c894-17f5-4fd1-be79-cb4900590418',授权:'https://login.microsoftonline.com/e7ee4711-c0b1-4311-b500-b80d89e5b298'},缓存:{缓存位置:'localStorage',storeAuthStateInCookie: 真}};如果 (!clientApplication){clientApplication = new Msal.UserAgentApplication(msalConfig);}} ());(异步函数 main() {//在下方添加您的 BOT IDvar BOT_ID = "ec06d968-e213-4d13-90c7-fe78bcadbfa6";var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;var userId = clientApplication.account?.accountIdentifier != null ?("您自定义的前缀" + clientApplication.account.accountIdentifier).substr(0, 64): (Math.random().toString() + Date.now().toString()).substr(0,64);const { token } = await fetchJSON(theURL);//console.log("主内的令牌:" + JSON.parse(token));const directLine = window.WebChat.createDirectLine({ token });//console.log("directLine inside main:" + directLine);const store = WebChat.createStore({}, ({ dispatch }) => next => action => {const { type } = action;//console.log("存储在 main:" + JSON.parse(store));如果(action.type === 'DIRECT_LINE/CONNECT_FULFILLED'){派遣({类型:'WEB_CHAT/SEND_EVENT',有效载荷:{名称:'开始对话',类型:'事件',价值:{文字:你好"}}});返回下一个(动作);}如果(action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'){常量活动 = action.payload.activity;让resourceUri;if (activity.from && activity.from.role === 'bot' && (resourceUri = getOAuthCardResourceUri(activity))){exchangeTokenAsync(resourceUri).then(function (token) {如果(令牌){//console.log("内部 if token:" + token);directLine.postActivity({类型:'调用',名称:'登录/令牌交换',价值:{id:activity.attachments[0].content.tokenExchangeResource.id,连接名称:activity.attachments[0].content.connectionName,令牌},从":{id:用户ID,名称:clientApplication.account.name,角色:用户"}}).subscribe(id => {if (id === '重试'){//bot 无法处理调用,因此显示 oauthCard返回下一个(动作);}//else: tokenexchange 成功,我们不显示 oauthCard},错误=>{//显示 oauthCard 时发生错误返回下一个(动作);});返回;}否则返回下一个(动作);});}否则返回下一个(动作);}否则返回下一个(动作);});常量样式选项 = {//添加 styleOptions 自定义网络聊天画布隐藏上传按钮:真};window.WebChat.renderWebChat({直连线:直连线,店铺,用户ID:用户ID,样式选项},document.getElementById('webchat'));})().catch(err => console.error("发生错误:" + err));</脚本></身体></html>

每次单击 Power Virtual Agent 按钮时,我都会在机器人中看到登录 OAuth 卡.

聊天机器人 OAuth 卡片图片

我正在寻找的是我不希望显示登录 OAuth 卡.相反,我想直接登录,因为我已经登录了我的 SharePoint 网站.

我在 Azure AAD 中创建了 2 个应用程序注册,1 个用于身份验证,另一个用于 SSO,遵循 文档 由微软提供

解决方案

很遗憾,SharePoint Online 无法与机器人服务进行 SharePoint SSO 集成,因此限制性更强.无法从 SharePoint 获取 AAD 令牌并发送到机器人以供使用.

作为一种解决方法,您只能将登录用户的 UserID 传递给机器人,以便它可以通过调用图形 API/SharePoint API 来获取访问令牌以使用资源,以再次获取新的访问令牌以进行访问使用 Bot 中的应用权限的用户/SP 详细信息.

但是这种方法存在安全隐患,任何拥有 Bot 直连令牌的人都可以传递任何有效的电子邮件 ID,并且它会进一步使用 Bot 中的 AAD 应用权限获取该用户(提供的电子邮件 ID)详细信息的访问令牌.但最终这些用户和电子邮件 ID 只能来自您组织的租户.我不能说它是安全/防暴的.

这只是一种解决方法,因为 SharePoint Online 无法与机器人服务集成 SharePoint SSO.如果您不希望用户使用 AAD 重新进行身份验证,则此(解决方法)是目前唯一可用的选项.

I am trying to configure SSO using the below code.

  <!DOCTYPE html>
<html>
<head>
<title>Contoso Sample Web Chat</title>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.0/js/msal.js"></script>
<script src="https://unpkg.com/@azure/storage-blob@10.3.0/browser/azure-storage.blob.min.js"
  integrity="sha384-fsfhtLyVQo3L3Bh73qgQoRR328xEeXnRGdoi53kjo1uectCfAHFfavrBBN2Nkbdf"
  crossorigin="anonymous">
</script>
<script type="text/javascript">
  if (typeof Msal === 'undefined') document.write(unescape("%3Cscript src='https://alcdn.msauth.net/lib/1.2.0/js/msal.js' type='text/javascript' %3E%3C/script%3E"));
</script>

  <style>
  html,
  body {
    height: 100%;
  }

  body {
    margin: 0;
  }

  .modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    padding-top: 100px; /* Location of the box */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0, 0, 0); /* Fallback color */
    background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
  }

  .modal-content {
    background-color: #fefefe;
    margin: auto;
    padding: 10px;
    border: 1px solid #888;
    width: 500px;
    height: 575px;
  }
  .close {
    color: black;
    float: right;
    font-size: 28px;
    font-weight: bold;
  }

  .close:hover,
  .close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
  }

  .main {
    margin: 18px;
    border-radius: 4px;
  }

  div[role="form"] {
    background-color: #3392ff;
  }

  #webchat {
    position: center;
    height: 530px;
    width: 100%;
    top: 60px;
    overflow: hidden;
  }
  #heading {
    padding-bottom: 5px;
  }

  h1 {
    font-size: 14px;
    font-family: Segoe UI;
    font-style: normal;
    font-weight: 600;
    font-size: 14px;
    line-height: 20px;
    color: #f3f2f1;
    letter-spacing: 0.005em;
    display: table-cell;
    vertical-align: middle;
    padding: 13px 0px 0px 20px;
  }

  /*#chatwindow
{
  height: 530px;
  width: 100%;
  overflow: hidden;
top: 60px;
 position: center;
}*/

  #login {
    position: fixed;
    margin-left: 150px;
  }

  .span {
    font-weight: bold;
  }
  #myBtn {
    position: fixed;
    float: right;
    outline: none;
    width: 60px;
    height: 80px;
    margin: auto auto auto 10px;
  }
  button:hover {
    background-color: transparent;
  }
  </style>

</head>

<body>
  <button id="myBtn" type="button">Power Virtual Agent</button>
  <div id="myModal" class="modal">
  <div class="modal-content" style="background-color: #ffd933">
     <span class="close">&times;</span>
<div id="chatwindow">
  <div id="heading">
    <div><span>SSO Test Bot</span></div>
  </div>
  <!-- <div style="z-index: 100;position: absolute;margin-top: 50px;width: 100%;">
     <div>
      <label id="userName" name="userName" style="width:75%;height:15px;border-color: Transparent;">Not logged in.</label>
      <button id="login" name="login" onclick="onSignInClick()">Log In</button>
    </div>
  </div> -->
  <div id="webchat"></div>
</div>
</div>
</div>



<script>
  //Button code begins here
  // Get the modal
  var modal = document.getElementById("myModal");

  // Get the button that opens the modal
  var btn = document.getElementById("myBtn");

  // Get the <span> element that closes the modal
  var span = document.getElementsByClassName("close")[0];

  // When the user clicks the button, open the modal
  btn.onclick = function () {
    modal.style.display = "block";
  };

  // When the user clicks on <span> (x), close the modal
  span.onclick = function () {
    modal.style.display = "none";
  };

  // When the user clicks anywhere outside of the modal, close it
  window.onclick = function (event) {
    if (event.target == modal) {
      modal.style.display = "none";
    }
  };
  //Button code ends here
</script>



<script>

function onSignin(idToken)
{
  alert("KMT - Inside onSignin: " + idToken);
  let user = clientApplication.getAccount();
  alert("KMT - user.name: " + user.name);
  document.getElementById("userName").innerHTML = "Currently logged in as " + user.name;
  let requestObj1 = {
    scopes: ["user.read", 'openid', 'profile']
  };
}

function onSignInClick()
{
  //console.log("Inside onSignInClick");
  let requestObj = {
    scopes: ["user.read", 'openid', 'profile']
  };

  clientApplication.loginPopup(requestObj).then(onSignin).catch(function (error) {console.log(error) });
}

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 AAD
         return activity.attachments[0].content.tokenExchangeResource.uri;
   }
}

function exchangeTokenAsync(resourceUri) {
  let user = clientApplication.getAccount();
  if (user) {
     let requestObj = {
       scopes: ["user.read", 'openid', 'profile']
     };
  return clientApplication.acquireTokenSilent(requestObj).then(function (tokenResponse) {
    return tokenResponse.accessToken;
     })
     .catch(function (error) {
       console.log(error);
     });
     }
     else {
     return Promise.resolve(null);
   }
}

async function fetchJSON(url, options = {}) {
    const res = await fetch(url, {
      ...options,
      headers: {
           ...options.headers,
           accept: 'application/json'
      }});

      if (!res.ok)
      {
        throw new Error(`Failed to fetch JSON due to ${res.status}`);
      }

      return await res.json();
  }
</script>

<script>
     var clientApplication;
     (function ()
     {
       var msalConfig = {
         auth:{
               clientId: '7dd5c894-17f5-4fd1-be79-cb4900590418',
               authority: 'https://login.microsoftonline.com/e7ee4711-c0b1-4311-b500-b80d89e5b298'
         },
         cache:{
               cacheLocation: 'localStorage',
               storeAuthStateInCookie: true
         }};
       if (!clientApplication)
       {
            clientApplication = new Msal.UserAgentApplication(msalConfig);
       }
     } ());

(async function main() {

  // Add your BOT ID below

  var BOT_ID = "ec06d968-e213-4d13-90c7-fe78bcadbfa6";
  var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;

    var userId = clientApplication.account?.accountIdentifier != null ?
                    ("You-customized-prefix" + clientApplication.account.accountIdentifier).substr(0, 64)
                    : (Math.random().toString() + Date.now().toString()).substr(0,64);

  const { token } = await fetchJSON(theURL);
  //console.log("Token inside main: " + JSON.parse(token));

  const directLine = window.WebChat.createDirectLine({ token });
  //console.log("directLine inside main: " + directLine);

  const store = WebChat.createStore({}, ({ dispatch }) => next => action => {const { type } = action;
  //console.log("store inside main: " + JSON.parse(store));

  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)
          {
            //console.log("Inside if token: " + 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')
                        {   // bot 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>
</body>
</html>

Every time I click on the Power Virtual Agent button I see the login OAuth card in the bot.

Chatbot OAuth card image

What I am looking for is I do not want the login OAuth card to be displayed. Instead I wanted to be logged in directly as I have already login to my SharePoint website.

I have created 2 App registrations in Azure AAD 1 for Authentication and the other for SSO following the document provided by Microsoft

解决方案

Unfortunately, SharePoint SSO integration with bot service is not possible with SharePoint Online, which is much more restrictive. There is no way to get the AAD token from SharePoint and send to the bot for use.

As a workaround, you can only pass the UserID of logged in user to the bot, so that it can fetch the access token to use resources by invoking the graph API/SharePoint API, to again fetch the new access token to access user/SP details using App permissions in the Bot.

But there is security catch with this approach, anyone who has the Bot directline token, can pass any valid email id, and further it will fetch access token for that user’s (supplied email id) details using AAD app permission in the Bot. But ultimately those users and email id can only be from your organization's tenant. I can't say that it's secure/ temper proof.

This is just a workaround as SharePoint SSO integration with bot service is not possible with SharePoint Online. If you don’t want users to re-authenticate with AAD, this (workaround) is the only available option at the moment.

这篇关于如何为网站上部署的聊天机器人配置 SSO的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆