Bot Framework-登录卡,如何获取身份验证结果 [英] Bot Framework - Sign-In Card, how get auth result
问题描述
使用Microsoft Bot Framework V3,我开始使用登录卡.
working with Microsoft Bot Framework V3 I started using Sign-In Cards.
我从示例代码页进行了简单的剪切并将其粘贴到我的代码中,然后说它可以工作(编译): https://docs.botframework.com/zh-cn/csharp/builder/sdkreference/attachments.html
I did a simple cut and paste from example code page into my code and let's say it works (compiles): https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html
预期的行为类似于oauth流程,因此将其重定向到,做自己的事情,并返回包括所有信息的auth resul.
What was expected is a behavior similar to oauth process so to be redirected to , do it's own stuffs and return the auth resul including all informations.
我意识到,它只是打开了一个新网页,指向我提供的链接,仅此而已...
What I realized is that it simply open a new web page to the link I provided, that's all...
没有其他代码可以建立...
No other code founded elsewere...
到目前为止,这似乎毫无用处,因为我可以基于此行为仅向链接提供正常消息,也没有与bot通信.
So far it seems useless as I could provide the link simply with normal messages based on this behavior, also there is no communication with the bot.
我错过了什么吗?
推荐答案
选项1)使用Windows Active Directory的自定义身份验证
我已经制定了一种自定义身份验证技术,该技术使用Kerberos LDAP协议和PrincipalContext类查询Windows AD.
I have made a custom authentication technique which queries Windows AD using Kerberos LDAP Protocol and using PrincipalContext class.
首先,在根对话框"中,将聊天的上下文保存在ConversationReference中,并使用Base64编码对其进行编码.
Firstly, in Root Dialog save the context of the chat in the ConversationReference and encode it using Base64 encoding.
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);
}
}
}
接下来,我们进入身份验证"对话框,在其中创建登录卡,并提供需要通过单击身份验证"按钮打开的URL页面.
Next, we come to the Auth Dialog in which we create a Sign-In Card and give the URL page that needs to be opened on the click of the authenticate button.
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);
}
}
}
接下来,我制作了一个MVC视图,该视图输入了您的用户名和密码,并将其发送到ADAuthController以针对Windows Active Directory进行查询.
Next I made a MVC view which inputs your username and password and sends it to the ADAuthController to query it against the 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();
}
}
}
接下来,我创建了一个简单的Razor视图,该视图使用jQuery AJAX调用来发送用户名和密码,方法是使用Javascript的btoa()函数对用户名和密码进行编码.
Next I created a simple Razor view which uses jQuery AJAX call to send username and password by encoding it in base64 encoding by using Javascript's btoa() function.
<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>
我制作了一个模型类来存储是否标识了用户.
I made a model class to store whether a user is identified or not.
namespace ADAuthService.Models
{
public class AuthenticatedUser
{
public string AuthenticatedUserName { get; set; } = string.Empty;
public bool IsAuthenticated { get; set; } = false;
}
}
和一个模型类,以从MVC View获取详细信息.
and a model class to get details from MVC View.
namespace ADAuthService.Models
{
public class UserDetailsHashed
{
public string HashedUserCredentials { get; set; } = string.Empty;
public string ConversationReference { get; set; } = string.Empty;
}
}
现在的主要内容是编写一种通过使用用户名,密码和域作为输入来查询Windows Active Directory的方法.验证后,我使用服务URL通过使用Autofac IoC容器解析作用域来将已验证的用户名发送给bot框架.
Now the main content is to write a method which queries the Windows Active Directory by taking username, password and domain as input. After authenticating I am using the Service URL to send the authenticated user's name to the bot framework by resolving the scope using Autofac IoC Container.
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.") });
}
}
}
}
选项2)使用以下链接中描述的模式: