首页 > 技术文章 > 使用OkHttp模拟登陆LeetCode

ZhaoxiCheung 2018-07-18 00:32 原文

前言

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

推荐阅读