使用OkHttp模擬登陸LeetCode

Zhaoxi_Zhang發表於2018-12-12

前言

網上有很多模擬登陸 LeetCode 的教程,但是基本都是使用 Python 來實現的。作為一個 Java 語言愛好者,因此想用 Java 來實現下。在實現的過程中,也遇到了一些坑點,故在此作為記錄。

過程

根據瀏覽器F12分析登陸頁面

使用OkHttp模擬登陸LeetCode
從上圖可以看出,LeetCode 生成一個 token ,然後在登陸的時候帶上這個資訊,因此我們模擬登陸的大致思路:首先獲取得到 cookie(包含有token),然後在登陸的時候帶上這個 cookie 資訊,完成 LeetCode 的驗證機制,進行模擬登陸。 但是直接進行模擬帶上 login(使用者名稱)、password(密碼)、csrfmiddlewaretoken(驗證資訊)是失敗的,提示 Forbidden。思考無果,另開思路。 用 fiddler 進行抓包,未登陸狀態,資料如下圖(也可用瀏覽器F12來進行分析):
使用OkHttp模擬登陸LeetCode
登陸狀態,資料如下圖,從圖中,我們可以發現,其 Content-Type 欄位與我們之前常見的值不一樣,其是 multipart/form-data 格式,因此我們在模擬登陸,要將其考慮進來。
使用OkHttp模擬登陸LeetCode
建立一個 multipart/form-data 的媒體格式,然後生成我們要的請求體,至於我們要的是哪種請求體,其格式可以從 fiddler 抓包的結果獲悉。 在SyntaxView中具體的詳情如下:
使用OkHttp模擬登陸LeetCode
值得注意的是 Content-Type 中的boundary只有四個“-”,而在請求體中有六個“-”,之前因為忽略了這個,一直被拒絕訪問(挺坑爹的

public static final String boundary = "----WebKitFormBoundaryhG2vKxp7y2GAwhPX";
public static final MediaType MULTIPART = MediaType.parse("multipart/form-data; boundary=" + boundary);
String form_data = "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"csrfmiddlewaretoken\"" + "\r\n\r\n"
                + csrftoken + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"login\"" + "\r\n\r\n"
                + usrname + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"password\"" + "\r\n\r\n"
                + passwd + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"next\"" + "\r\n\r\n"
                + "/problems" + "\r\n"
                + "--" + boundary + "--";
        RequestBody requestBody = RequestBody.create(MULTIPART,form_data);
複製程式碼

##結果 將其返回的報文列印出來,得到如下資訊則表示模擬登陸成功

使用OkHttp模擬登陸LeetCode
從 fiddler 的抓包結果中也可以證實這點
使用OkHttp模擬登陸LeetCode

程式碼

package LeetCodeLogin;

import okhttp3.*;
import org.jsoup.Connection;
import org.jsoup.Jsoup;


import java.io.IOException;
import java.util.*;


import static java.lang.System.out;

public class Login {
    public static final String boundary = "----WebKitFormBoundaryhG2vKxp7y2GAwhPX";
    public static final MediaType MULTIPART = MediaType.parse("multipart/form-data; boundary=" + boundary);
    public static void main(String... args) throws IOException {
        Scanner scanner = new Scanner(System.in);


        String url = "https://leetcode.com/accounts/login/";
        String usrname = "xxx";
        String passwd = "xxx";

        Connection.Response response1 = Jsoup.connect(url)
                .method(Connection.Method.GET)
                .execute();

        String csrftoken = response1.cookie("csrftoken");
        String __cfduid = response1.cookie("__cfduid");
        out.println("csrftoken = " + csrftoken);
        out.println("__cfduid = " + __cfduid );

        OkHttpClient client = new OkHttpClient().newBuilder()
                .followRedirects(false)
                .followSslRedirects(false)
                .build();


        String form_data = "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"csrfmiddlewaretoken\"" + "\r\n\r\n"
                + csrftoken + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"login\"" + "\r\n\r\n"
                + usrname + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"password\"" + "\r\n\r\n"
                + passwd + "\r\n"
                + "--" + boundary + "\r\n"
                + "Content-Disposition: form-data; name=\"next\"" + "\r\n\r\n"
                + "/problems" + "\r\n"
                + "--" + boundary + "--";
        RequestBody requestBody = RequestBody.create(MULTIPART,form_data);



        Request request = new Request.Builder()
                .addHeader("Content-Type", "multipart/form-data; boundary=" + boundary)
                .addHeader("Connection","keep-alive")
                .addHeader("Accept","*/*")
                .addHeader("Origin","https://leetcode.com")
                .addHeader("Referer",url)
                .addHeader("Cookie","__cfduid=" + __cfduid + ";" + "csrftoken=" + csrftoken)
                .post(requestBody)
                .url(url)
                .build();

        Response response = client.newCall(request).execute();
        out.println(response.message());
        out.println(response.headers());
        out.println(response.body().string());
    }
}
複製程式碼

需要注意的是,在上述程式碼中,我們通過下述程式碼禁止了重定向,來自己處理重定向請求,可參考使用OkHttp進行重定向攔截處理,若是沒有進行重定向攔截,也會使得模擬登陸失敗。

    .followRedirects(false)
    .followSslRedirects(false)
複製程式碼

寫在最後

本次模擬登陸,雖然程式碼很簡單,但是確實也經歷了一些波折,比對過 Python 和 Js 寫的模擬登陸的程式碼,用 Java 來進行模擬似乎多了一些瑣碎的細節,對於具體的為何 Python 和 Js 能如此簡介的處理的原理還在琢磨中。此次,也得到了朋友 faberry 的幫助,在一些地方給了意見。

相關文章