前言
网上有很多模拟登陆 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 的帮助,在一些地方给了意见。