face api协议分析

来源:互联网 发布:java 天气预报接口 编辑:程序博客网 时间:2024/06/05 06:02
应用编号
1675194565824091
Account Kit 应用密匙
91f1de0b6fb697a92e6a89761d40dbb3
Account Kit 客户端口令
3fd5c567b4996d4f45ed43cebc535a2a

先有两个大问题,一个是这个线程交互实现的是什么。
另一个是从主线恢复类功能,并尽可能少的使用函数。

登录资料和头像自动填写

批量注册gmail

EMAIL_LOGIN_COMPLETE
event {
{
name EMAIL_LOGIN_COMPLETE
hashcode 1116123282
offset 0
count 20
}
ordinal 3
}


  if(contentController instanceof EmailLoginContentController) {
                            manager2 = intent.getStringExtra(EXTRA_EMAIL);//这里也是获取之前putextra()打包内容
                            EmailLoginFlowManager manager5 = (EmailLoginFlowManager)AccountKitActivity.this.loginFlowManager;
                            ((ActivityEmailHandler)manager5.getActivityHandler()).onEmailLoginComplete(AccountKitActivity.this, manager5, manager2);
                        }

public void onEmailLoginComplete(AccountKitActivity activity, EmailLoginFlowManager emailManager, String email) {
        activity.pushState(LoginFlowState.SENDING_CODE, (OnPushListener)null);
        emailManager.setEmail(email);//这里只是设置参数的
        emailManager.logInWithEmail(this.configuration.getResponseType(), this.configuration.getInitialAuthState());
    }
参数一是个句柄没啥用,参数二控制流似乎只是控制跳转方式控制的 参数三 请求的email地址这个重要

 public ResponseType getResponseType() {
        return this.responseType;//responseType; "TOKEN"
    }
responseType
value="token"
name="TOKEN"
ordinal=1

 public String getInitialAuthState() {
        return this.initialAuthState;//null
    }

 public void logInWithEmail(ResponseType responseType, @Nullable String initialAuthState) {//TOKEN null
        if(this.isValid() && this.email != null) {
            AccountKitController.logInWithEmail(this.email, responseType.getValue(), initialAuthState);//这三个值分别是取得的email token null
        }
    }

 public static EmailLoginModel logInWithEmail(String email, String responseType, @Nullable String initialAuthState) {
        if(getCurrentAccessToken() != null) {
            logOut();
        }

        return initializer.getLoginManager().logInWithEmail(email, responseType, initialAuthState);
    }

 LoginManager getLoginManager() {
        Validate.sdkInitialized();
        return this.data.loginManager;
    }

 static void sdkInitialized() {
        if(!AccountKit.isInitialized()) {
            throw new AccountKitException(Type.INITIALIZATION_ERROR, InternalAccountKitError.SDK_NOT_INITIALIZED);
        }
    }
public static boolean isInitialized() {
        return AccountKitController.isInitialized();
    }
public static boolean isInitialized() {
        return initializer.isInitialized();
    }
public boolean isInitialized() {
        return this.state == Initializer.State.INITIALIZED;//这里检测一下状态 state:"INITIALIZED"
    }


这里比较重要了com.facebook.accountkit.internal;final class LoginManager
 EmailLoginModelImpl logInWithEmail(@NonNull String email, @NonNull String responseType, @Nullable String initialAuthState) {
        Utility.assertUIThread();
        this.cancelExisting();
        EmailLoginModelImpl loginModel = new EmailLoginModelImpl(email, responseType);//一个类
        EmailLoginController loginHandler = new EmailLoginController(this.accessTokenManager, this, loginModel);
        loginHandler.logIn(initialAuthState);
        this.onLoginStart(loginModel);
        this.currentLoginController = loginHandler;
        return loginModel;
    }

onLoginStart
 public void logLoginModel(String eventName, LoginModelImpl loginModel) {


AccountKitController这个类是最开始进入的internal包
然后是LoginManager的loginwithEmail;这里看下初始化的时候参数是啥。。。。。。。。



看了下协议中很多界面交互的和数据传输的设计,跟核心的服务器交互关系不大,关系最紧的是那个graphrequest和graphresponse,但这个应该不是直接调用的关键。我现在想跟踪email的具体执行流,把这个调用request和response的核心类给找出来应该对分析有很大帮助。

accountpreference
appevent
EmailLoginController   //目测这个类里面的login函数是登录函数需要动态跟进一下
ExperimentionConfiguration
LoginController
LoginManager

emailLoginController.class
login函数里
        ((EmailLoginModelImpl)this.loginModel).setInitialAuthState(initialAuthState);
        AccountKitGraphRequest graphRequest = this.buildGraphRequest("start_login", parameters);//这里结合上面的赋值初始化了graphRequest
        AccountKitGraphRequestAsyncTask.cancelCurrentAsyncTask();
        AccountKitGraphRequestAsyncTask task = AccountKitGraphRequest.executeAsync(graphRequest, requestCallback);
        AccountKitGraphRequestAsyncTask.setCurrentAsyncTask(task);


下面这个函数通过异步和回调调用,貌似是连接服务器的接受数据的函数,位置AccountKitGraphResponse类
static AccountKitGraphResponse fromHttpConnection(HttpURLConnection connection, AccountKitGraphRequest request) {
        InputStream stream = null;

        AccountKitGraphResponse var4;
        try {
            if(connection.getResponseCode() >= 400) {
                stream = connection.getErrorStream();
            } else {
                stream = connection.getInputStream();
            }

            AccountKitGraphResponse exception = createResponseFromStream(stream, connection, request);
            return exception;
        } catch (AccountKitException var9) {
            ConsoleLogger.log(LoggingBehavior.REQUESTS, "AccountKitGraphResponse", "Response <ERROR>: %s", new Object[]{var9});
            var4 = new AccountKitGraphResponse(request, connection, new AccountKitRequestError(var9));
        } catch (IOException | SecurityException | JSONException var10) {
            ConsoleLogger.log(LoggingBehavior.REQUESTS, "AccountKitGraphResponse", "Response <ERROR>: %s", new Object[]{var10});
            var4 = new AccountKitGraphResponse(request, connection, new AccountKitRequestError(new AccountKitException(Type.SERVER_ERROR, var10)));
            return var4;
        } finally {
            Utility.closeQuietly(stream);
        }

        return var4;
    }

AccountKitGraphRequestAsyncTsk类中通过一系列设置,启动异步线程执行数据交互
protected AccountKitGraphResponse doInBackground(Void... params) {
        try {
            return this.connection == null?this.request.executeAndWait():AccountKitGraphRequest.executeConnectionAndWait(this.connection, this.request);
        } catch (Exception var3) {
            this.exception = var3;
            return null;
        }
    }
我们这里看到executeConnectionAndWait执行连接是要有一个connect,和request的,但这里是连接后的返回结果,我们需要找到请求的过程。
应该是个回调函数。。。。。。。。。
  public interface Callback {
        void onCompleted(AccountKitGraphResponse var1);
    }
这个是request类里的回调接口。



emailLoginController.class
login函数里
        ((EmailLoginModelImpl)this.loginModel).setInitialAuthState(initialAuthState);
        AccountKitGraphRequest graphRequest = this.buildGraphRequest("start_login", parameters);//这里结合上面的赋值初始化了graphRequest
        AccountKitGraphRequestAsyncTask.cancelCurrentAsyncTask();
        AccountKitGraphRequestAsyncTask task = AccountKitGraphRequest.executeAsync(graphRequest, requestCallback);
        AccountKitGraphRequestAsyncTask.setCurrentAsyncTask(task);
我们有回到了这个登录管理流
buildgraph目测只是实现了初始化各种参数
第二句应该是取消之前执行的异步执行
第三句函数通过这两个参数初始化了类,也没有回调函数的定义
那么我们就要看下这个requestCallback到底是哪里来的了。

一看之下发现,回调函数正是紧挨上面的代码,也在login函数中
 Callback requestCallback = new Callback() {
            public void onCompleted(AccountKitGraphResponse response) {
                LoginManager loginManager = EmailLoginController.this.getLoginManager();
                if(loginManager != null) {
                    try {
                        if(response.getError() != null) {
                            Pair result1 = Utility.createErrorFromServerError(response.getError());
                            EmailLoginController.this.onError((AccountKitError)result1.first);
                        } else {
                            JSONObject result = response.getResponseObject();
                            if(result == null) {
                                EmailLoginController.this.onError(Type.LOGIN_INVALIDATED, InternalAccountKitError.NO_RESULT_FOUND);
                            } else {
                                String privacyPolicy = result.optString("privacy_policy");
                                if(!Utility.isNullOrEmpty(privacyPolicy)) {
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).putField("privacy_policy", privacyPolicy);
                                }

                                String termsOfService = result.optString("terms_of_service");
                                if(!Utility.isNullOrEmpty(termsOfService)) {
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).putField("terms_of_service", termsOfService);
                                }

                                String expiresInString;
                                long expiresIn;
                                try {
                                    boolean e = result.getBoolean("can_attempt_seamless_login");
                                    expiresInString = result.getString("expires_at");
                                    expiresIn = Long.parseLong(expiresInString) * 1000L;
                                    if(e && expiresIn > System.currentTimeMillis()) {
                                        ((EmailLoginModelImpl)EmailLoginController.this.loginModel).setStatus(LoginStatus.ACCOUNT_VERIFIED);
                                        return;
                                    }
                                } catch (JSONException var17) {
                                    ;
                                }

                                try {
                                    String e1 = result.getString("login_request_code");
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).setLoginCode(e1);
                                    expiresInString = result.getString("expires_in_sec");
                                    expiresIn = Long.parseLong(expiresInString);
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).setExpiresInSeconds(expiresIn);
                                    String intervalSecondsString = result.getString("interval_sec");
                                    int intervalSeconds = Integer.parseInt(intervalSecondsString);
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).setInterval(intervalSeconds);
                                    ((EmailLoginModelImpl)EmailLoginController.this.loginModel).setStatus(LoginStatus.PENDING);
                                    loginManager.handle(EmailLoginController.this.loginModel);
                                } catch (NumberFormatException | JSONException var16) {
                                    EmailLoginController.this.onError(Type.LOGIN_INVALIDATED, InternalAccountKitError.INVALID_GRAPH_RESULTS_FORMAT);
                                }

                            }
                        }
                    } finally {
                        EmailLoginController.this.broadcastLoginStateChange();
                    }
                }
            }
        };
回调函数中有个关键参数AccountKitGraphResponse response。这个应该包含了对服务器的请求过程,应为整个回调函数中只是对相应的各种判断。
回调函数的返回值Callback requestCallback恰巧是AccountKitGraphRequestAsyncTask task = AccountKitGraphRequest.executeAsync(graphRequest, requestCallback);的第二个参数,上面我们知道第一个参数仅仅包含一些参数的初始化。

这里又到了异步请求的类,这里的参数result会被引用到callback回调函数作为参数,需要找到
 protected void onPostExecute(AccountKitGraphResponse result)
 
根据异步任务调用原则,我们的onPostExecute在执行的参数将会是DoInBackgroud的返回值。如下:
protected AccountKitGraphResponse doInBackground(Void... params) {
        try {
            return this.connection == null?this.request.executeAndWait():AccountKitGraphRequest.executeConnectionAndWait(this.connection, this.request);//这里这个三目运算符比较奇怪,按照定义,条件一为空则代表条件一为0.我们总是会执行条件三的表达式,哪只写一个表达式不就行了
        } catch (Exception var3) {
            this.exception = var3;
            return null;
        }
    }
正常情况下不会返回空。

AccountKitGraphResponse executeAndWait() {
        HttpURLConnection connection;
            connection = toHttpConnection(this);//参数初始化
        AccountKitGraphResponse response = executeConnectionAndWait(connection, this);//启动请求
            return response;
    }

 static HttpURLConnection toHttpConnection(AccountKitGraphRequest request) {
        URL url;
            String connection = request.getUrlForSingleRequest();
            url = new URL(connection);
        

            HttpURLConnection connection1 = createConnection(url);
            serializeToUrlConnection(request, connection1);//请求参数初始化
            return connection1;
    }

static AccountKitGraphResponse executeConnectionAndWait(HttpURLConnection connection, AccountKitGraphRequest request) {
        AccountKitGraphResponse response = AccountKitGraphResponse.fromHttpConnection(connection, request);//启动请求
        Utility.disconnectQuietly(connection);
        return response;
    }

 static AccountKitGraphResponse fromHttpConnection(HttpURLConnection connection, AccountKitGraphRequest request) {
        InputStream stream = null;

        AccountKitGraphResponse var4;
        try {
            if(connection.getResponseCode() >= 400) {
                stream = connection.getErrorStream();
            } else {
                stream = connection.getInputStream();//这里是请求的关键,连接到了服务器得到流
            }

            AccountKitGraphResponse exception = createResponseFromStream(stream, connection, request);//创建了响应流
            return exception;
        } finally {
            Utility.closeQuietly(stream);
        }

        return var4;
    }

这里比较可疑
stream = connection.getInputStream();
这个HttpURLConnection connection是个java.net包里的函数。所以貌似可疑伪造

http://blog.csdn.net/it_oracle/article/details/7076636




 private static void serializeToUrlConnection(AccountKitGraphRequest request, HttpURLConnection connection) throws IOException, JSONException {
        ConsoleLogger consoleLogger = new ConsoleLogger(LoggingBehavior.REQUESTS, "Request");
        HttpMethod connectionHttpMethod = request.httpMethod;
        connection.setRequestMethod(connectionHttpMethod.name());
        boolean isMultipart = isMultiPart(request.parameters);
        setConnectionContentType(connection, isMultipart);
        URL url = connection.getURL();
我们看到所有的参数都在控制范围内,但有一个参数例外
request.parameter
这个参数我分析了很久
他经过了很多复杂的查询和赋值,本来我想尽量少的改动源代码,可惜。这个赋值涉及到很多android源码的加密解密,和类操作,根本抠不出来。
当然不计代价,肯定能实现,但这里我不会这么做。
我们动态跟踪下
parameter里面有很多成员,我们把关于android系统相关的元素忽略。然后可以看到有个,mMap如下:
"fb_app_events_enabled" -> "false"
"response_type" -> "token"
"credentials_type" -> "email"
"redirect_uri" -> "ak964234977027033://authorize"
"email" -> "inquisiter@163.com"
"locale" -> "zh_CN"
"sdk" -> "android"
"access_token" -> "AA|964234977027033|799de49b357b77afede08488ced2f721"
"logging_ref" -> "2fc145f1-1137-43ee-a1ae-1cc945ccc5a4"
"fields" -> "terms_of_service,privacy_policy"
这里有很多有实际意义的参数
但我没只关心email的值,其他的没有必要动态生成,直接设置成固定参数
所以思路来了

URL=https://graph.accountkit.com/v1.2/start_login

isMultipart=false

private String getUrlForSingleRequest() throws MalformedURLException {
        URL builder = new URL("https://graph.accountkit.com");//这里对URL进行编码
        Matcher matcher = versionPattern.matcher(this.graphPath);
       /*if(!matcher.matches()) {
            builder.appendPath(this.version);
        }

        builder.appendPath(this.graphPath);
        //this.addCommonParameters();
        if(this.httpMethod != HttpMethod.POST) {
            this.appendQueryParametersToUri(builder);
        }
*/
        return builder.toString();
    }

我们发现toHttpConnection调用getUrlForSingleRequest
  static HttpURLConnection toHttpConnection(AccountKitGraphRequest request) {
        URL url;
        try {
            String connection = request.getUrlForSingleRequest();
            url = new URL(connection);
        } catch (MalformedURLException var6) {
            throw new AccountKitException(Type.INTERNAL_ERROR, InternalAccountKitError.CANNOT_CONSTRUCT_URL, var6);
        }

        try {
            HttpURLConnection connection1 = createConnection(url);
            serializeToUrlConnection(request, connection1);
            return connection1;
        } catch (UnknownHostException var4) {
            throw new AccountKitException(Type.NETWORK_CONNECTION_ERROR, InternalAccountKitError.NO_NETWORK_CONNECTION);
        } catch (JSONException | IOException var5) {
            throw new AccountKitException(Type.INTERNAL_ERROR, InternalAccountKitError.CANNOT_CONSTRUCT_MESSAGE_BODY, var5);
        }
    }
这一段有很多的请求参数
而getUrlsingleRequest明显是用来获取服务器相应目录资源的。这里说实话动态构造这个比较麻烦。
我们根据多方的分析这里应该是请求参数构造的关键
不过没有必要非按这个还原,因为确实比较麻烦。我们的思路是构建url连接就可以了,把关键参数抠出来就行了。
唯一要确定的是有没有什么序列化的对象呗传输到接口中了。

所以我把重点还是要放在serializeToUrlConnection这个函数上。

最终我们得到的核心交互位于这里
AccountKitGraphResponse executeAndWait() {
        HttpURLConnection connection;
            connection = toHttpConnection(this);//参数初始化,这里可能有序列化的东西
        AccountKitGraphResponse response = executeConnectionAndWait(connection, this);//启动请求并返回数据,这里可以参看我们的请求参数到底有些什么
            return response;
    }
toHttpConnection--》serializeToUrlConnection--》connection.getOutputStream();
executeConnectionAndWait---》fromHttpConnection--》connection.getInputStream();
这里其实就圆满了。我们把这个动态跟踪并伪造相应的结构发出请求。大功告成。


如果有序列化的东西,我们需要寻找OutputStream outputStream()对象的write函数,应该在getoutstream()附近。



try {
                OutputStream outputStream1 = connection.getOutputStream();
                outputStream = new BufferedOutputStream(outputStream1);
                if(!isMultipart) {
                    outputStream = new GZIPOutputStream((OutputStream)outputStream);
                }

                processRequest(request, (OutputStream)outputStream, isMultipart);

然而我们只找到个processRequest,

 private static void processRequest(AccountKitGraphRequest request, OutputStream outputStream, boolean isMultipart) throws IOException {
        AccountKitGraphRequest.Serializer serializer = new AccountKitGraphRequest.Serializer(outputStream, !isMultipart);
        serializeParameters(request.parameters, serializer);
        if(request.requestObject != null) {
            processRequestObject(request.requestObject, serializer);
        }

    }

private static class Serializer implements AccountKitGraphRequest.KeyValueSerializer {
        private boolean firstWrite = true;
        private final OutputStream outputStream;
        private boolean useUrlEncode = false;

        Serializer(OutputStream outputStream, boolean useUrlEncode) {
            this.outputStream = outputStream;
            this.useUrlEncode = useUrlEncode;
        }

这里有个接口Serializers实现,服了。
writeObject(String key, Object value)
writeString(String key, String value)
writeBitmap(String key, Bitmap bitmap)
writeBytes(String key, byte[] bytes)
writeContentUri(String key, Uri contentUri, String mimeType)
writeFile(String key, ParcelFileDescriptor descriptor, String mimeType)
这几个接口貌似是实现序列化写入的。
并且不约而同的调用了函数
writeContentDisposition(String name, String filename, String contentType)

而此函数有调用了outputstreaml类的write函数。虽然这其中还有一些小波折,但总体是这个意思。

这里还有两个封装了write的函数
 void write(String format, Object... args) throws IOException {
            if(!this.useUrlEncode) {
                if(this.firstWrite) {
                    this.outputStream.write("--".getBytes());
                    this.outputStream.write("3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f".getBytes());
                    this.outputStream.write("\r\n".getBytes());
                    this.firstWrite = false;
                }

                this.outputStream.write(String.format(format, args).getBytes());
            } else {
                this.outputStream.write(URLEncoder.encode(String.format(Locale.US, format, args), "UTF-8").getBytes());
            }

        }

        void writeLine(String format, Object... args) throws IOException {
            this.write(format, args);
            if(!this.useUrlEncode) {
                this.write("\r\n", new Object[0]);
            }

        }
    }
接着分析发现
 private static void processRequest(AccountKitGraphRequest request, OutputStream outputStream, boolean isMultipart) throws IOException {
        AccountKitGraphRequest.Serializer serializer = new AccountKitGraphRequest.Serializer(outputStream, !isMultipart);
        serializeParameters(request.parameters, serializer);
        if(request.requestObject != null) {
            processRequestObject(request.requestObject, serializer);
        }

    }
我们的outputstream流会通过serializeParameters把bundle 类 request.parameters参数赋进去,也就是说我们的序列化对象通过serializeParameters传输

还有个参数request.requestObject ,这是一个JSONObject类,我们知道这个协议通信就是基于这东西的。虽然目前我们无法迅速找到这个参数赋值的情况,但我们只需动态调试一下,把我们看到的参数分析一下就行了。
 private static void processRequestObject(JSONObject requestObject, AccountKitGraphRequest.KeyValueSerializer serializer) throws IOException {
        Iterator keyIterator = requestObject.keys();

        while(keyIterator.hasNext()) {
            String key = (String)keyIterator.next();
            Object value = requestObject.opt(key);
            processRequestObjectProperty(key, value, serializer);
        }

    }

private static void processRequestObjectProperty(String key, Object value, AccountKitGraphRequest.KeyValueSerializer serializer) throws IOException {
        Class valueClass = value.getClass();
        if(!String.class.isAssignableFrom(valueClass) && !Number.class.isAssignableFrom(valueClass) && !Boolean.class.isAssignableFrom(valueClass)) {
            if(Date.class.isAssignableFrom(valueClass)) {
                Date date = (Date)value;
                SimpleDateFormat iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ssZ", Locale.US);
                serializer.writeString(key, iso8601DateFormat.format(date));
            }
        } else {
            serializer.writeString(key, value.toString());
        }

    }