Bot框架 - 登录卡,如何获得验证结果

Jan*_*ano 5 bots botframework

使用Microsoft Bot Framework V3我开始使用登录卡.

我做了一个简单的剪切和粘贴从示例代码页到我的代码,让我们说它工作(编译):https: //docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html

所期望的是类似于oauth过程的行为,因此被重定向到,做它自己的东西并返回包括所有信息的auth结果.

我意识到它只是打开一个新的网页到我提供的链接,这就是...

没有其他代码建立其他...

到目前为止它似乎没用,因为我可以根据这种行为简单地提供正常消息的链接,也没有与机器人的通信.

我错过了什么吗?

Kun*_*jee 6

选项1)使用Windows Active Directory的自定义身份验证

我已经制定了一种自定义身份验证技术,该技术使用Kerberos LDAP协议和PrincipalContext类查询Windows AD。

首先,在“根对话框”中,将聊天的上下文保存在ConversationReference中,并使用Base64编码对其进行编码。

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;

namespace ADAuthBot.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {
        public async Task StartAsync(IDialogContext context)
        {
            await context.PostAsync("Welcome to Auth Bot!");
            context.Wait(MessageReceivedAsync);
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var message = await result as Activity;
            ConversationReference conversationReference = message.ToConversationReference();

            string username = string.Empty;

            context.PrivateConversationData.SetValue<string>("usertext", message.Text);
            if (!context.PrivateConversationData.TryGetValue<string>("Username", out username))
            {
                string encodedCookie = UrlToken.Encode(conversationReference);
                await AuthDialog.createPromptForLogin(context, encodedCookie);
            }

            else
            {
                context.Call(this, ResumeAfter);
            }


        }

        private async Task ResumeAfter(IDialogContext context, IAwaitable<object> result)
        {
            var item = await result;
            context.Wait(MessageReceivedAsync);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们进入“身份验证”对话框,在其中创建登录卡,并提供需要通过单击身份验证按钮打开的URL页面。

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;
using System.Collections.Generic;
using System.Configuration;

namespace ADAuthBot.Dialogs
{
    [Serializable]
    public class AuthDialog: IDialog<object>
    {
        static string authenticationUrl = string.Empty;  //Authentication URL is the MVC View URL, which will have the username and password window.
        static string callbackurl = string.Empty;
        static AuthDialog()
        {
            authenticationUrl = ConfigurationManager.AppSettings["AuthenticationUrl"];
            callbackurl = ConfigurationManager.AppSettings["AuthCallbackUrl"];
        }
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
        }

        public static async Task createPromptForLogin(IDialogContext context, string encodedCookie)
        {
            IMessageActivity response = context.MakeMessage();
            response.Attachments = new List<Attachment>();

            SigninCard signincard = new SigninCard()
            {
                Text = "Click here to sign in",
                Buttons = new List<CardAction>() {
                        new CardAction()
                        {
                            Title = "Authentication Required",
                            Type = ActionTypes.OpenUrl,
                            Value = $"{authenticationUrl}?{encodedCookie}"
                        }
                    }
            };

            response.Attachments.Add(signincard.ToAttachment());
            await context.PostAsync(response);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我制作了一个MVC视图,该视图输入您的用户名和密码,并将其发送到ADAuthController以针对Windows Active Directory进行查询。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace ADAuthService.Controllers
{
    public class LoginADController : Controller
    {
        // GET: LoginAD
        [Route("Login")]
        public ActionResult LoginUsingAD()
        {
            return View();
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我创建了一个简单的Razor视图,该视图使用jQuery AJAX调用来发送用户名和密码,方法是使用Javascript的btoa()函数对用户名和密码进行base64编码。

<script src="~/scripts/jquery-3.2.1.min.js"></script>
<script src="~/scripts/bootstrap.min.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />

<script>
        $(function () {
            $("#txtUserName").html("");
            $("#txtPassword").html("");


            function make_base64_auth(username, password) {
                var tok = username + ' ' + password;
                var hash = btoa(tok);
                return hash;
            }

            $("#btnSubmit").click(function () {
                var userName = $("#txtUserName").val();
                var passWord = $("#txtPassword").val();

                var conversationReference = $(location).attr('search');
                console.log(conversationReference);

                var dataToBeSent = {
                    "ConversationReference": conversationReference,
                    "HashedUserCredentials": make_base64_auth(userName, passWord)
                };

                $.ajax({
                    url: "http://localhost:1070/api/Login",
                    method: "POST",
                    dataType: "json",
                    data: dataToBeSent,
                    contentType: "application/json",
                    crossDomain: true,
                    success: function (data) {
                        debugger;
                        console.log(data);
                        if(!$.isEmptyObject(data))
                            alert(data);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        debugger;
                        if (!$.isEmptyObject(jqXHR))
                        alert("Something happened wrong because: " + jqXHR.responseText);
                    }
                });

            });
        });
</script>
<div class="panel-info">
    <div class="panel panel-heading">
        Enter your credentials
    </div>
    <div class="panel panel-body">
        <div class="form-group">
            <label for="username">Username: </label> <input id="txtUserName" type="text" placeholder="Enter username" required class="form-control" />
            <label for="password">Password: </label> <input id="txtPassword" type="password" placeholder="Enter password" required class="form-control" />
            <button id="btnSubmit" class="btn btn-info">Submit</button>
            <button id="btnReset" class="btn btn-danger" type="reset">Reset</button>
        </div>
    </div>
</div>
Run Code Online (Sandbox Code Playgroud)

我制作了一个模型类来存储是否标识了用户。

namespace ADAuthService.Models
{
    public class AuthenticatedUser
    {
        public string AuthenticatedUserName { get; set; } = string.Empty;
        public bool IsAuthenticated { get; set; } = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个模型类,以从MVC View获取详细信息。

namespace ADAuthService.Models
{
    public class UserDetailsHashed
    {
        public string HashedUserCredentials { get; set; } = string.Empty;
        public string ConversationReference { get; set; } = string.Empty;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在的主要内容是编写一种通过使用用户名,密码和域作为输入来查询Windows Active Directory的方法。身份验证后,我使用服务URL通过使用Autofac IoC容器解析范围来将已身份验证的用户名发送到bot框架。

using ADAuthService.Models;
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Cors;

namespace ADAuthService.Controllers
{
    public class ADAuthController : ApiController
    {
        [NonAction]
        private void extractUserDetailsFromHash(UserDetailsHashed userDetails, out string username, out string password, out string conversationReference)
        {
            try
            {
                string[] userCredentials = userDetails.HashedUserCredentials.Split(' ');
                byte[] userCredentialsBinary = Convert.FromBase64String(userCredentials.Last());
                string decodedString = Encoding.UTF8.GetString(userCredentialsBinary);
                string[] decodedStringArray = decodedString.Split(' ');
                username = decodedStringArray[0];
                password = decodedStringArray[1];
                string[] userConversationReference = userDetails.ConversationReference.Split('?');
                conversationReference = userConversationReference[1];
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        [NonAction]
        private Task<AuthenticatedUser> ValidateUserAgainstAD(string username, string password)
        {
            AuthenticatedUser user = new AuthenticatedUser();
            return Task.Run<AuthenticatedUser>(() => {
                string ADDisplayName = string.Empty;
                try
                {
                    using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, System.Environment.UserDomainName))
                    {
                        bool isValidCredentials = ctx.ValidateCredentials(username, password, ContextOptions.Negotiate);

                        // Additional check to search user in directory.
                        if (isValidCredentials)
                        {
                            UserPrincipal prUsr = new UserPrincipal(ctx);
                            prUsr.SamAccountName = username;
                            PrincipalSearcher srchUser = new PrincipalSearcher(prUsr);
                            UserPrincipal foundUsr = srchUser.FindOne() as UserPrincipal;

                            if (foundUsr != null)
                            {
                                user.AuthenticatedUserName = foundUsr.DisplayName;
                                user.IsAuthenticated = isValidCredentials;
                            }
                        }
                        else
                            throw new AuthenticationException($"Couldn't query no such credentials in Microsoft Active Directory such as Username: {username} and Password: {password}. Try entering a valid username and password combination.");
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                return user;
            });    
        }

        [NonAction]
        public async Task ReplyToBot(string userName, string encodedConversationReference)
        {
            Activity reply = null;
            ConversationReference decodedConversationReference = UrlToken.Decode<ConversationReference>(encodedConversationReference);
            bool writeSuccessful = false;
            IMessageActivity msgToBeSent = decodedConversationReference.GetPostToUserMessage();

            using (ILifetimeScope scope = DialogModule.BeginLifetimeScope(Conversation.Container, msgToBeSent))
            {
                try
                {
                    IConnectorClient client = scope.Resolve<IConnectorClient>();
                    IStateClient sc = scope.Resolve<IStateClient>();

                    BotData userData = sc.BotState.GetPrivateConversationData(msgToBeSent.ChannelId, msgToBeSent.From.Id, msgToBeSent.Id);
                    userData.SetProperty("Username", userName);
                    sc.BotState.SetPrivateConversationData(msgToBeSent.ChannelId, msgToBeSent.Conversation.Id, msgToBeSent.Id, userData);
                    writeSuccessful = true;
                }
                catch (Exception ex)
                {
                    writeSuccessful = false;
                    throw ex;
                }

                if (!writeSuccessful)
                {
                    msgToBeSent.Text = string.Empty;
                    await Conversation.ResumeAsync(decodedConversationReference, msgToBeSent);
                }
                if (writeSuccessful)
                {
                    reply = msgToBeSent as Activity;
                    var connector = new ConnectorClient(new Uri(msgToBeSent.ServiceUrl));
                    reply.Text = $"Welcome {userName}!";
                    connector.Conversations.SendToConversation(reply);
                }
            }
        }

        [HttpPost]
        [EnableCors("*", "*", "*")]
        [Route("api/Login")]
        public async Task<HttpResponseMessage> Login(UserDetailsHashed userDetails)
        {
            try
            {
                string username = string.Empty;
                string password = string.Empty;
                string conversationReference = string.Empty;
                AuthenticatedUser userToBeAuthenticated = new AuthenticatedUser();

                extractUserDetailsFromHash(userDetails, out username, out password, out conversationReference);
                userToBeAuthenticated =  await ValidateUserAgainstAD(username, password);


                if (userToBeAuthenticated.IsAuthenticated)
                {
                    await ReplyToBot(userName: userToBeAuthenticated.AuthenticatedUserName, encodedConversationReference: conversationReference);
                    return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent($"Thanks, {userToBeAuthenticated.AuthenticatedUserName} you're now logged in!") };
                }
                else
                {
                    return new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden, Content = new StringContent($"Couldn't query no such credentials in Microsoft Active Directory such as Username: {username} and Password: {password}. Try entering a valid username and password combination.") };
                }
            }
            catch(Exception ex)
            {
                throw new HttpResponseException(new HttpResponseMessage() { StatusCode = HttpStatusCode.Forbidden, Content = new StringContent($"Couldn't query no such credentials in Microsoft Active Directory. Try entering a valid username and password combination.") });
            }
        }        
    }
}
Run Code Online (Sandbox Code Playgroud)

选项2)使用以下链接中描述的模式:

MSDN幻数模式


Eze*_*dib 5

不,你没有错过任何东西.登录卡只是向用户显示需要进行身份验证的可视方式.每个频道将以不同方式显示登录卡; 取决于渠道实施.

要实施OAuth流程,我建议您查看AuthBot.

AuthBot是一个.Net库,用于通过Microsoft Bot Framework构建的bot上的Azure Active Directory身份验证.

即使您没有使用AAD,该库仍然可以帮助您了解如何实施OAuth流程.另外,AuthBot在某些情况下也使用登录卡来要求用户进行身份验证(请参阅此代码).

还有其他示例也可以帮助您了解如何构建OAuth流程: