整合Facebook聊天功能

16
我写了一个程序来在C#中集成Facebook用户聊天,但是每次向服务器发送响应后,我总是会得到<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized/></failure>的错误信息。我已经检查了API密钥和App密钥,它们都是正确的。看起来我正在向服务器传递一些错误的参数。下面是我的代码。
private void GetDetailsButton_Click(object sender, EventArgs e)
{
     TcpClient FacebookClient = new TcpClient();
     FacebookClient.Connect("chat.facebook.com", 5222);
     NetworkStream myns = FacebookClient.GetStream();

     string xml = "<?xml version='1.0'?>" +
     "<stream:stream " +
     "id='1' " +
     "to='chat.facebook.com' " +
     "xmlns='jabber:client' " +
     "xmlns:stream='http://etherx.jabber.org/streams' " +
     "version='1.0' >";

     StreamWriter mySw = new StreamWriter(myns);
     mySw.WriteLine(xml);  //sending initial request
     mySw.Flush();

     byte[] serverResponseByte = new byte[1024];
     int myBytesRead = 0;
     StringBuilder myResponseAsSB = new StringBuilder();

     //reading response from the server to see the supported authentication methods 
     do
     {
            myBytesRead = myns.Read(serverResponseByte, 0, serverResponseByte.Length);
            myResponseAsSB.Append(System.Text.Encoding.UTF8.GetString(serverResponseByte, 0, myBytesRead));

     } while (myns.DataAvailable);


     myResponseAsSB.Clear();

     xml = "<auth " +
     "xmlns='urn:ietf:params:xml:ns:xmpp-sasl' " +
     "mechanism='X-FACEBOOK-PLATFORM'  />";

     mySw.WriteLine(xml);
     mySw.Flush();   //sending response to server to use X-FACEBOOK-PLATFORM


     //reading challenge send by the server
     do
     {
          myBytesRead = myns.Read(serverResponseByte, 0, serverResponseByte.Length);
          myResponseAsSB.Append(System.Text.Encoding.UTF8.GetString(serverResponseByte, 0, myBytesRead));

     } while (myns.DataAvailable);


     myResponseAsSB.Replace("<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">", "");
     myResponseAsSB.Replace("</challenge>", "");

     //converting challenge string to normal string
     byte[] myregularstrigbytes = Convert.FromBase64String(myResponseAsSB.ToString());
     string myregularstring = System.Text.Encoding.UTF8.GetString(myregularstrigbytes);


     //I've hardcoded the accesstoken here for testing purpose. 
     string SessionKey = AccessToken.Split('|')[1]; 

     string response = ComposeResponse(myregularstring);

     byte[] myResponseByte = Encoding.UTF8.GetBytes(response.ToString());

     string myEncodedResponseToSend = Convert.ToBase64String(myResponseByte);
     xml = String.Format("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">{0}</response>", myEncodedResponseToSend);
     mySw.WriteLine(xml);
     mySw.Flush();   //sending the response to the server with my parameters

     myResponseAsSB.Clear();

     //checking if authentication succeed 
     do
     {
          myBytesRead = myns.Read(serverResponseByte, 0, serverResponseByte.Length);
          myResponseAsSB.Append(System.Text.Encoding.UTF8.GetString(serverResponseByte, 0, myBytesRead));

     } while (myns.DataAvailable);

     MessageBox.Show(myResponseAsSB.ToString());

}

    private string ComposeResponse(string serverresponse)
    {
         string version = serverresponse.Split('&')[0].Split('=')[1];
         string method = serverresponse.Split('&')[1].Split('=')[1];
         string nonce = serverresponse.Split('&')[2].Split('=')[1];
         string SessionKey = AccessToken.Split('|')[1];

         long callId = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;

         string sig = "api_key=" + appId
         + "call_id=" + callId
         + "method=" + method
         + "nonce=" + nonce
         + "session_key=" + SessionKey
         + "v=" + "1.0"
         + AppSecret;

         MD5 md = MD5.Create();
         var hash = md.ComputeHash(Encoding.UTF8.GetBytes(sig));

         sig = hash.Aggregate("", (current, b) => current + b.ToString("x2"));

         return "api_key=" + HttpUtility.UrlEncode(appId)
         + "&call_id=" + HttpUtility.UrlEncode(callId)
         + "&method=" + HttpUtility.UrlEncode(method)
         + "&nonce=" + HttpUtility.UrlEncode(nonce)
         + "&session_key=" + HttpUtility.UrlEncode(SessionKey)
         + "&v=" + HttpUtility.UrlEncode("1.0")
         + "&sig=" + HttpUtility.UrlEncode(sig);

    }
我参考了这篇文章 Facebook Chat Authentication in C#X-FACEBOOK-PLATFORM,我的应用程序类型是Native/Desktop。
有人能指点我正确的方向吗?
编辑:我认为问题出在创建签名时,是否有一种方法来验证创建的签名?
编辑1:根据这个SO答案,第一个|字符后访问令牌包含会话密钥,我可以找到|字符直到2天前,但现在我找不到|字符在访问令牌中,这真的很奇怪,那么我现在该如何找到会话密钥呢?(或许我应该去睡觉了。)
编辑2:奇怪的是,我总是以<appId>|<sessionKey>|<digest>形式获得本机/桌面应用程序的访问令牌。我进一步搜索并发现需要从auth.promoteSession遗留API中提取会话密钥,并使用HttpUtility.UrlEncode而不是HttpUtility.HtmlEncode对参数进行编码。
现在,我已经硬编码了访问令牌(在访问令牌调试器中进行了验证),会话密钥,App密钥和应用程序密码仍然出现相同的错误<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized/></failure> 编辑3:我已经苦思冥想了一个星期,但这仍然没有作用,但今天我在文档中发现了一个更新,其中说请注意,这需要通过TLS(传输层安全性)进行,否则将会产生错误。 我想我需要相应地修改我的代码。
编辑4:我尝试了文档中的代码,并发现$SESSION_XML的值应该是:
$SESSION_XML = '<iq type="set" id="4">'.
  '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>';

我完成转换后会发布C#代码。


2
哦,真有趣,你正在收到<not-authorized/>…因为出于某些奇怪的原因,这是我目前遇到的错误。感谢你提供了链接到我的博客! :) - Ian Quigley
用户是否已经授予您的应用程序 xmpp_login 扩展权限 - Casey Chu
1
另外,你确定你硬编码的访问令牌没有过期吗? - Casey Chu
@Casey Hope 我正在使用自己的账户,并已授权权限,甚至确保访问令牌未过期,但仍然无法工作。 - Searock
一些试探性的尝试——如果您删除HtmlEncode调用会发生什么?您哈希的参数应该与您传递的参数完全相同。另外,如果您不包括call_id参数会发生什么?一年前我写类似的代码时没有包括那个参数。 - Casey Chu
@Casey Hope 最初我没有使用HtmlEncode,但文档说应该进行Url编码,即使如此它仍然不起作用,我正在传递 call_id,因为文档中提到了它,看看如果我不传递call_id会发生什么:) - Searock
5个回答

13

要使用X-FACEBOOK-PLATFORM,您需要用户会话,该会话是使用传统的认证流程提供的。 然而,正如您在edit1中注意到的那样,access_token在|之后包含了用户会话。

我们在上一篇博客文章中宣布,access_token将被加密,并且从10月1日起将是强制性的。在此之前,可以在高级应用程序设置中切换选项。http://developers.facebook.com/blog/post/553/

未来,access_token将能够用于X-FACEBOOK-PLATFORM。


我是否必须使用传统的Connect Auth而不是OAuth 2.0身份验证? - Searock
这是最简单的方法,直到我们很快支持X-FACEBOOK-PLATFORM上的access_token。您需要进行一些小更改,以通过access_token替换session_key。就我个人而言,我宁愿通过从access_token中提取session_key的hacky方式来完成,这样当我们启用OAuth2.0时更改将最小化(在那之前,请在应用程序设置中禁用access_token加密)。 - Alexcode
这意味着我不必使用Legacy Connect Auth,只需要从访问令牌中提取会话密钥,对吗?我已经禁用了access_token的加密。 - Searock
没错,我就是这样做的。 - Alexcode
我也需要进行应用程序登录吗? - Searock
显示剩余4条评论

7
你会更快地抵达目的地,如果你从一个现有的XMPP库开始。这里有一个列表:http://xmpp.org/xmpp-software/libraries/ 例如,你很快就会希望你没有手写所有XML而是通过DOM运行它。同时,请使用以下字符测试您的所有输入:<>'"&

感谢您的建议,我肯定会使用一些XML解析器。 - Searock
什么是通过字符 <>'"& 进行测试输入?我应该检查是否我的任何服务器响应包含这些字符吗? - Searock
1
我的意思是,除非您正在使用XML DOM,否则您很可能无法在所有需要的地方正确转义它们,如&lt;&gt;&apos;&quot;&amp; - Joe Hildebrand

5

我看过这段 Python 代码,但是无法理解,我想这是唯一的解决办法,我会再次查看它。 - Searock
我在哪里可以找到pyxmpp文件?网络上有没有特定的文件? - Searock
www.google.com/search?q=pyxmpp - 第一个结果可能是它:http://pyxmpp.jajcus.net/ - Igy

1

我不确定这是否是问题,也没有时间进行测试,但是您的签名看起来对我来说有些错误。此外,请注意Facebook在9月初转移到了OAuth 2.0,因此请确保您的access_token是正确的。

尝试这个:

string sig = "api_key=" + appId
     + "call_id=" + callId
     + "method=" + method
     + "nonce=" + nonce
     + "session_key=" + SessionKey
     + "v=1.0"
     + APP_SECRET;

MD5 md = MD5.Create();
var hash = md.ComputeHash(Encoding.UTF8.GetBytes(sig));
sig = hash.Aggregate("", (current, b) => current + b.ToString("x2"));

var response = "api_key=" + appId
     + "&call_id=" + callId
     + "&method=" + method
     + "&nonce=" + nonce
     + "&session_key=" + SessionKey
     + "&v=1.0";

 // response & signature
 response = string.Concat(Uri.EscapeDataString(response),
                         "&", Uri.EscapeDataString(sig))

 // base64 encode
 var myEncodedResponseToSend = Convert.ToBase64String(
                                        ASCIIEncoding.ASCII.GetBytes(response));

1

之前我也遇到了使用X-FACEBOOK-PLATFORM进行身份验证的问题。我找到了一个解决方案,我认为可以帮助你。

使用支持X-FACEBOOK-PLATFORM的Java Asmack库进行XMPP

虽然代码是Java,但很容易理解它的工作原理。诀窍是使用您的OAuth 2.0令牌从Facebook获取会话秘密密钥。您必须以以下方式使用一个已弃用的方法auth.promoteSession:

https://api.facebook.com/method/auth.promoteSession?access_token=yourAccessToken

接下来,Facebook会提供给你一个会话密钥,你需要将其作为应用程序密钥参数使用。要使用此方法,您必须在编辑设置部分和高级选项卡中禁用删除弃用的API参数。

希望这可以帮助到你。


谢谢回复。我已经尝试过了,但由于某些原因它对我没有起作用。现在Facebook支持加密访问令牌,您不再需要传递签名。请查看文档http://developers.facebook.com/docs/chat/。 - Searock
@Searock 当我遇到这个问题时,我查看了那个页面。对我来说,在我使用auth.promoteSession方法并使用会话密钥作为应用程序所需的秘钥之后,这才开始工作。但是,如果您确定已经尝试过这样做,那么我不知道为什么它不起作用。 - Adrian
4
@Adrian,X-FACEBOOK-PLATFORM 的 OAuth2.0 支持于昨天刚刚增加:http://developers.facebook.com/blog/post/555/ session_key 和 sig 被 access_token 替换。 - Alexcode
是的,你说得对。谢谢你指出来。我会尝试让它正常工作的。 - Adrian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接