XMPP с библиотекой Java Asmack, поддерживающей X-FACEBOOK-PLATFORM

Я пытаюсь сделать Facebook-чат на Android с помощью библиотеки Smack. Я прочитал Chat API из Facebook, но я не могу понять, как мне пройти аутентификацию с помощью 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> 

Как говорится в форуме разработчиков Facebook , необходимо отключить параметр «Отключить устаревшие методы аутентификации» на странице настроек Facebook вашего приложения. Но, даже делая это, я не могу войти. А ключ сеанса – вторая часть токена OAuth в форме AAA | BBB | CCC, я имею в виду BBB.

Solutions Collecting From Web of "XMPP с библиотекой Java Asmack, поддерживающей X-FACEBOOK-PLATFORM"

Наконец, благодаря кодексу no.good.at.coding и предложению harism, я смог подключиться к чату Facebook. Этот код является механизмом для библиотеки Asmack (порт Smack для Android). Для библиотеки 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 – это ключ API, указанный на странице настроек приложения в Facebook. SessionKey – вторая часть токена доступа. Если токен в этой форме, AAA | BBB | CCC, BBB – это ключ сеанса. SessionSecret получается с использованием старого REST API с помощью метода auth.promoteSession. Чтобы использовать его, необходимо сделать Http для этого URL:

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

Несмотря на то, что в документации на Facebook сообщается, что необходимо использовать секретный ключ вашего приложения, только когда я использовал ключ, который вернул этот метод REST, я смог заставить его работать. Чтобы этот метод работал, вы должны отключить опцию « Отключить устаревшие параметры» на вкладке «Предварительный просмотр» в настройках вашего приложения.

Я использовал это около 6 месяцев назад с помощью Smack (не asmack), поэтому я не уверен, как он задержит сейчас, но здесь, надеюсь, это поможет!

Я нашел реализацию механизма аутентификации Facebook X-FACEBOOK-PLATFORM на форуме Ignite Realtime Smack, где кто-то получил его из проекта fbgc . Вы найдете ZIP с источником SASLXFacebookPlatformMechanism.java в ответе, с которым я связан. Вы можете использовать его следующим образом:

 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 с параметром code . Я открываю URLConnection для https://graph.facebook.com/oauth/access_token?client_id=<app_id>&redirect_uri=http://myserver/context/path/&client_secret=<app_secret>&code=<code> . Ответ должен быть токеном доступа, где идентификатор сеанса является частью после | – что-то вроде формы XXX|<sessionId> .

Вот код, который я успешно использовал для аутентификации. Возможно, это помогает, хотя это никак не связано с Smack. Вы можете получить sessionKey из токена доступа, полученного от FB, и для получения sessionSecret я использую старый API REST;

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(); } 

Извините, что я написал новый ответ, но мне пришлось включить новый код @YShinkarev извините за опоздание
Изменив @Adrian ответ, чтобы сделать challengeReceived, мы можем использовать APIKey и accessToken, все, что я изменил, было составленоResponse

 @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)); } 

Что ты хочешь делать?

Если вы просто хотите войти в FB-чат, вы подключаетесь к FB точно так же, как и любой другой сервер XMPP.

Я бы посмотрел и использовал «Authenticating with Username / Password» из Chat API, который поддерживается Smack. Если я не хочу писать приложение FaceBook. Затем я попробую войти в систему с помощью «Аутентификация с помощью платформы Facebook».

Итак, просто используйте Smack для подключения к чату FB, как это было бы с вашим обычным клиентом Jabber.

  1. Для имени пользователя используйте свое имя пользователя Facebook. (См. http://www.facebook.com/username/ )
  2. Для домена используйте: chat.facebook.com
  3. Для пароля используйте свой пароль Facebook
  4. Отключить SSL и TSL
  5. Установите порт подключения на: 5222 (который по умолчанию используется для XMPP)
  6. Установите сервер подключений на chat.facebook.com