我正在尝试使用Smack库在Android上进行Facebook聊天。我从Facebook上看过Chat API,但我无法理解如何使用此库对Facebook进行身份验证。
有人能指出我如何做到这一点吗?
更新:根据no.good.at.coding答案,我将此代码改编为Asmack库。一切正常,但我收到的是对登录的回复:未经授权。这是我使用的代码:
public class SASLXFacebookPlatformMechanism extends SASLMechanism
{
private static final String NAME = "X-FACEBOOK-PLATFORM";
private String apiKey = "";
private String applicationSecret = "";
private String sessionKey = "";
/**
* Constructor.
*/
public SASLXFacebookPlatformMechanism(SASLAuthentication saslAuthentication)
{
super(saslAuthentication);
}
@Override
protected void authenticate() throws IOException, XMPPException
{
getSASLAuthentication().send(new AuthMechanism(NAME, ""));
}
@Override
public void authenticate(String apiKeyAndSessionKey, String host,
String applicationSecret) throws IOException, XMPPException
{
if (apiKeyAndSessionKey == null || applicationSecret == null)
{
throw new IllegalArgumentException("Invalid parameters");
}
String[] keyArray = apiKeyAndSessionKey.split("\\|", 2);
if (keyArray.length < 2)
{
throw new IllegalArgumentException(
"API key or session key is not present");
}
this.apiKey = keyArray[0];
Log.d("API_KEY", apiKey);
this.applicationSecret = applicationSecret;
Log.d("SECRET_KEY", applicationSecret);
this.sessionKey = keyArray[1];
Log.d("SESSION_KEY", sessionKey);
this.authenticationId = sessionKey;
this.password = applicationSecret;
this.hostname = host;
String[] mechanisms = { "DIGEST-MD5" };
Map<String, String> props = new HashMap<String, String>();
this.sc =
Sasl.createSaslClient(mechanisms, null, "xmpp", host, props,
this);
authenticate();
}
@Override
protected String getName()
{
return NAME;
}
@Override
public void challengeReceived(String challenge) throws IOException
{
byte[] response = null;
if (challenge != null)
{
String decodedChallenge = new String(Base64.decode(challenge));
Log.d("DECODED", decodedChallenge);
Map<String, String> parameters = getQueryMap(decodedChallenge);
String version = "1.0";
String nonce = parameters.get("nonce");
String method = parameters.get("method");
long callId = new GregorianCalendar().getTimeInMillis() / 1000L;
String sig =
"api_key=" + apiKey + "call_id=" + callId + "method="
+ method + "nonce=" + nonce + "session_key="
+ sessionKey + "v=" + version + applicationSecret;
try
{
sig = md5(sig);
sig = sig.toUpperCase();
} catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
String composedResponse =
"api_key=" + URLEncoder.encode(apiKey, "utf-8")
+ "&call_id=" + callId + "&method="
+ URLEncoder.encode(method, "utf-8") + "&nonce="
+ URLEncoder.encode(nonce, "utf-8")
+ "&session_key="
+ URLEncoder.encode(sessionKey, "utf-8") + "&v="
+ URLEncoder.encode(version, "utf-8") + "&sig="
+ URLEncoder.encode(sig, "utf-8");
Log.d("COMPOSED", composedResponse);
response = composedResponse.getBytes("utf-8");
}
String authenticationText = "";
if (response != null)
{
authenticationText =
Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
}
// Send the authentication to the server
getSASLAuthentication().send(new Response(authenticationText));
}
private Map<String, String> getQueryMap(String query)
{
Map<String, String> map = new HashMap<String, String>();
String[] params = query.split("\\&");
for (String param : params)
{
String[] fields = param.split("=", 2);
map.put(fields[0], (fields.length > 1 ? fields[1] : null));
}
return map;
}
private String md5(String text) throws NoSuchAlgorithmException,
UnsupportedEncodingException
{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(text.getBytes("utf-8"), 0, text.length());
return convertToHex(md.digest());
}
private String convertToHex(byte[] data)
{
StringBuilder buf = new StringBuilder();
int len = data.length;
for (int i = 0; i < len; i++)
{
int halfByte = (data[i] >>> 4) & 0xF;
int twoHalfs = 0;
do
{
if (0 <= halfByte && halfByte <= 9)
{
buf.append((char) ('0' + halfByte));
}
else
{
buf.append((char) ('a' + halfByte - 10));
}
halfByte = data[i] & 0xF;
} while (twoHalfs++ < 1);
}
return buf.toString();
}
}
这就是与服务器通过发送和接收消息的通信:
PM SENT (1132418216): <stream:stream to="chat.facebook.com" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
PM RCV (1132418216): <?xml version="1.0"?><stream:stream id="C62D0F43" from="chat.facebook.com" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xml:lang="en"><stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>X-FACEBOOK-PLATFORM</mechanism><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features>
PM SENT (1132418216): <auth mechanism="X-FACEBOOK-PLATFORM" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></auth>
PM RCV (1132418216): <challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9NzFGNkQ3Rjc5QkIyREJCQ0YxQTkwMzA0QTg3OTlBMzM=</challenge>
PM SENT (1132418216): <response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">YXBpX2tleT0zMWYzYjg1ZjBjODYwNjQ3NThiZTZhOTQyNjVjZmNjMCZjYWxsX2lkPTEzMDA0NTYxMzUmbWV0aG9kPWF1dGgueG1wcF9sb2dpbiZub25jZT03MUY2RDdGNzlCQjJEQkJDRjFBOTAzMDRBODc5OUEzMyZzZXNzaW9uX2tleT0yNjUzMTg4ODNkYWJhOGRlOTRiYTk4ZDYtMTAwMDAwNTAyNjc2Nzc4JnY9MS4wJnNpZz04RkRDRjRGRTgzMENGOEQ3QjgwNjdERUQyOEE2RERFQw==</response>
PM RCV (1132418216): <failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized/></failure>
正如developers Facebook forum中所述,需要从应用的Facebook设置页面禁用“禁用弃用的身份验证方法”设置。但是,即使这样做,我也无法登录。会话密钥是OAuth令牌的第二部分,形式为AAA | BBB | CCC,我的意思是BBB。
答案 0 :(得分:14)
最后,由于no.good.at.coding代码和harism的建议,我已经能够连接到Facebook聊天。此代码是Asmack库(Android的Smack端口)的机制。对于Smack库,必须使用no.good.at.coding机制。
SASLXFacebookPlatformMechanism.java:
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
import org.apache.harmony.javax.security.sasl.Sasl;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.util.Base64;
public class SASLXFacebookPlatformMechanism extends SASLMechanism
{
private static final String NAME = "X-FACEBOOK-PLATFORM";
private String apiKey = "";
private String applicationSecret = "";
private String sessionKey = "";
/**
* Constructor.
*/
public SASLXFacebookPlatformMechanism(SASLAuthentication saslAuthentication)
{
super(saslAuthentication);
}
@Override
protected void authenticate() throws IOException, XMPPException
{
getSASLAuthentication().send(new AuthMechanism(NAME, ""));
}
@Override
public void authenticate(String apiKeyAndSessionKey, String host,
String applicationSecret) throws IOException, XMPPException
{
if (apiKeyAndSessionKey == null || applicationSecret == null)
{
throw new IllegalArgumentException("Invalid parameters");
}
String[] keyArray = apiKeyAndSessionKey.split("\\|", 2);
if (keyArray.length < 2)
{
throw new IllegalArgumentException(
"API key or session key is not present");
}
this.apiKey = keyArray[0];
this.applicationSecret = applicationSecret;
this.sessionKey = keyArray[1];
this.authenticationId = sessionKey;
this.password = applicationSecret;
this.hostname = host;
String[] mechanisms = { "DIGEST-MD5" };
Map<String, String> props = new HashMap<String, String>();
this.sc =
Sasl.createSaslClient(mechanisms, null, "xmpp", host, props,
this);
authenticate();
}
@Override
public void authenticate(String username, String host, CallbackHandler cbh)
throws IOException, XMPPException
{
String[] mechanisms = { "DIGEST-MD5" };
Map<String, String> props = new HashMap<String, String>();
this.sc =
Sasl.createSaslClient(mechanisms, null, "xmpp", host, props,
cbh);
authenticate();
}
@Override
protected String getName()
{
return NAME;
}
@Override
public void challengeReceived(String challenge) throws IOException
{
byte[] response = null;
if (challenge != null)
{
String decodedChallenge = new String(Base64.decode(challenge));
Map<String, String> parameters = getQueryMap(decodedChallenge);
String version = "1.0";
String nonce = parameters.get("nonce");
String method = parameters.get("method");
long callId = new GregorianCalendar().getTimeInMillis();
String sig =
"api_key=" + apiKey + "call_id=" + callId + "method="
+ method + "nonce=" + nonce + "session_key="
+ sessionKey + "v=" + version + applicationSecret;
try
{
sig = md5(sig);
} catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
String composedResponse =
"api_key=" + URLEncoder.encode(apiKey, "utf-8")
+ "&call_id=" + callId + "&method="
+ URLEncoder.encode(method, "utf-8") + "&nonce="
+ URLEncoder.encode(nonce, "utf-8")
+ "&session_key="
+ URLEncoder.encode(sessionKey, "utf-8") + "&v="
+ URLEncoder.encode(version, "utf-8") + "&sig="
+ URLEncoder.encode(sig, "utf-8");
response = composedResponse.getBytes("utf-8");
}
String authenticationText = "";
if (response != null)
{
authenticationText =
Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
}
// Send the authentication to the server
getSASLAuthentication().send(new Response(authenticationText));
}
private Map<String, String> getQueryMap(String query)
{
Map<String, String> map = new HashMap<String, String>();
String[] params = query.split("\\&");
for (String param : params)
{
String[] fields = param.split("=", 2);
map.put(fields[0], (fields.length > 1 ? fields[1] : null));
}
return map;
}
private String md5(String text) throws NoSuchAlgorithmException,
UnsupportedEncodingException
{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(text.getBytes("utf-8"), 0, text.length());
return convertToHex(md.digest());
}
private String convertToHex(byte[] data)
{
StringBuilder buf = new StringBuilder();
int len = data.length;
for (int i = 0; i < len; i++)
{
int halfByte = (data[i] >>> 4) & 0xF;
int twoHalfs = 0;
do
{
if (0 <= halfByte && halfByte <= 9)
{
buf.append((char) ('0' + halfByte));
}
else
{
buf.append((char) ('a' + halfByte - 10));
}
halfByte = data[i] & 0xF;
} while (twoHalfs++ < 1);
}
return buf.toString();
}
}
使用它:
ConnectionConfiguration config = new ConnectionConfiguration("chat.facebook.com", 5222);
config.setSASLAuthenticationEnabled(true);
XMPPConnection xmpp = new XMPPConnection(config);
try
{
SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM", SASLXFacebookPlatformMechanism.class);
SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0);
xmpp.connect();
xmpp.login(apiKey + "|" + sessionKey, sessionSecret, "Application");
} catch (XMPPException e)
{
xmpp.disconnect();
e.printStackTrace();
}
apiKey是Facebook中应用程序设置页面中给出的API密钥。 sessionKey是访问令牌的第二部分。如果令牌是这种形式,AAA | BBB | CCC,则BBB是会话密钥。 sessionSecret是使用旧的REST API和auth.promoteSession方法获得的。要使用它,需要让Http到达这个URL:
https://api.facebook.com/method/auth.promoteSession?access_token=yourAccessToken
尽管Facebook Chat文档说它需要使用你的应用程序密钥,但只有当我使用返回该REST方法的密钥时我才能使它工作。要使该方法有效,您必须在应用程序设置的“高级”选项卡中禁用禁用不推荐使用的身份验证方法选项。
答案 1 :(得分:4)
大约6个月前我和Smack(不是asmack)使用了这个,所以我不确定它现在会怎么样,但是这里有,希望它有所帮助!
我在Ignite Realtime Smack forum找到了Facebook的X-FACEBOOK-PLATFORM认证机制的实现,有人从fbgc project获得了它。您会在我链接的答案中找到包含SASLXFacebookPlatformMechanism.java
来源的ZIP。您可以按如下方式使用它:
public void login() throws XMPPException
{
SASLAuthentication.registerSASLMechanism(SASLXFacebookPlatformMechanism.NAME,
SASLXFacebookPlatformMechanism.class);
SASLAuthentication.supportSASLMechanism(SASLXFacebookPlatformMechanism.NAME, 0);
ConnectionConfiguration connConfig = new ConnectionConfiguration(host, port);
XMPPConnection connection = new XMPPConnection(connConfig);
connection.connect();
log.info("XMPP client connected");
connection.login(Utils.FB_APP_ID + "|" + this.user.sessionId, Utils.FB_APP_SECRET, "app_name");
log.info("XMPP client logged in");
}
我在没有SDK的情况下在服务器上执行此操作。我不记得细节(并且Facebook文档不是很好)但是从我的代码中可以看出,在让用户授权应用程序之后,我收到来自Facebook的回调请求{{1}参数。我打开code
到URLConnection
。响应应该是访问令牌,其中会话ID是https://graph.facebook.com/oauth/access_token?client_id=<app_id>&redirect_uri=http://myserver/context/path/&client_secret=<app_secret>&code=<code>
之后的部分 - |
形式的部分。
答案 2 :(得分:3)
这是我成功用于身份验证的代码。也许这有帮助,即使这与Smack没有任何关系。你可以从FB收到的访问令牌中获取sessionKey,并获取sessionSecret我一直在使用旧的REST API;
http://developers.facebook.com/docs/reference/rest/auth.promoteSession/
private final void processChallenge(XmlPullParser parser, Writer writer,
String sessionKey, String sessionSecret) throws IOException,
NoSuchAlgorithmException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "challenge");
String challenge = new String(Base64.decode(parser.nextText(),
Base64.DEFAULT));
String params[] = challenge.split("&");
HashMap<String, String> paramMap = new HashMap<String, String>();
for (int i = 0; i < params.length; ++i) {
String p[] = params[i].split("=");
p[0] = URLDecoder.decode(p[0]);
p[1] = URLDecoder.decode(p[1]);
paramMap.put(p[0], p[1]);
}
String api_key = "YOUR_API_KEY";
String call_id = "" + System.currentTimeMillis();
String method = paramMap.get("method");
String nonce = paramMap.get("nonce");
String v = "1.0";
StringBuffer sigBuffer = new StringBuffer();
sigBuffer.append("api_key=" + api_key);
sigBuffer.append("call_id=" + call_id);
sigBuffer.append("method=" + method);
sigBuffer.append("nonce=" + nonce);
sigBuffer.append("session_key=" + sessionKey);
sigBuffer.append("v=" + v);
sigBuffer.append(sessionSecret);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sigBuffer.toString().getBytes());
byte[] digest = md.digest();
StringBuffer sig = new StringBuffer();
for (int i = 0; i < digest.length; ++i) {
sig.append(Integer.toHexString(0xFF & digest[i]));
}
StringBuffer response = new StringBuffer();
response.append("api_key=" + URLEncoder.encode(api_key));
response.append("&call_id=" + URLEncoder.encode(call_id));
response.append("&method=" + URLEncoder.encode(method));
response.append("&nonce=" + URLEncoder.encode(nonce));
response.append("&session_key=" + URLEncoder.encode(sessionKey));
response.append("&v=" + URLEncoder.encode(v));
response.append("&sig=" + URLEncoder.encode(sig.toString()));
StringBuilder out = new StringBuilder();
out.append("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
out.append(Base64.encodeToString(response.toString().getBytes(),
Base64.NO_WRAP));
out.append("</response>");
writer.write(out.toString());
writer.flush();
}
答案 3 :(得分:1)
我很抱歉得到新答案,但我不得不包含新代码@YShinkarev抱歉迟到了 通过修改@Adrian的答案来进行challengeReceived我们可以使用APIKey和accessToken所有我修改的是composedResponse
@Override
public void challengeReceived(String challenge) throws IOException {
byte[] response = null;
if (challenge != null) {
String decodedChallenge = new String(Base64.decode(challenge));
Map<String, String> parameters = getQueryMap(decodedChallenge);
String version = "1.0";
String nonce = parameters.get("nonce");
String method = parameters.get("method");
long callId = new GregorianCalendar().getTimeInMillis();
String composedResponse = "api_key="
+ URLEncoder.encode(apiKey, "utf-8") + "&call_id=" + callId
+ "&method=" + URLEncoder.encode(method, "utf-8")
+ "&nonce=" + URLEncoder.encode(nonce, "utf-8")
+ "&access_token="
+ URLEncoder.encode(access_token, "utf-8") + "&v="
+ URLEncoder.encode(version, "utf-8");
response = composedResponse.getBytes("utf-8");
}
String authenticationText = "";
if (response != null) {
authenticationText = Base64.encodeBytes(response,
Base64.DONT_BREAK_LINES);
}
// Send the authentication to the server
getSASLAuthentication().send(new Response(authenticationText));
}
答案 4 :(得分:0)
你想做什么?
如果您只想登录FB聊天,就像任何其他XMPP服务器一样连接到FB。
我会查看并使用Chat API中的“使用用户名/密码进行身份验证”,Smack支持。除非我想写一个FaceBook应用程序。然后我会尝试使用“使用Facebook平台进行身份验证”登录。
因此,只需使用Smack连接到FB聊天,就像使用普通的Jabber客户端一样。