前言
網上有很多模擬登陸 LeetCode 的教程,但是基本都是使用 Python 來實現的。作為一個 Java 語言愛好者,因此想用 Java 來實現下。在實現的過程中,也遇到了一些坑點,故在此作為記錄。
過程
根據瀏覽器F12分析登陸頁面
從上圖可以看出,LeetCode 生成一個 token ,然後在登陸的時候帶上這個資訊,因此我們模擬登陸的大致思路:首先獲取得到 cookie(包含有token),然後在登陸的時候帶上這個 cookie 資訊,完成 LeetCode 的驗證機制,進行模擬登陸。 但是直接進行模擬帶上 login(使用者名稱)、password(密碼)、csrfmiddlewaretoken(驗證資訊)是失敗的,提示 Forbidden。思考無果,另開思路。 用 fiddler 進行抓包,未登陸狀態,資料如下圖(也可用瀏覽器F12來進行分析): 登陸狀態,資料如下圖,從圖中,我們可以發現,其 Content-Type 欄位與我們之前常見的值不一樣,其是 multipart/form-data 格式,因此我們在模擬登陸,要將其考慮進來。 建立一個 multipart/form-data 的媒體格式,然後生成我們要的請求體,至於我們要的是哪種請求體,其格式可以從 fiddler 抓包的結果獲悉。 在SyntaxView中具體的詳情如下: 值得注意的是 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);
複製程式碼
##結果 將其返回的報文列印出來,得到如下資訊則表示模擬登陸成功
從 fiddler 的抓包結果中也可以證實這點程式碼
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 的幫助,在一些地方給了意見。