文章目录
  1. 1. 初始化
  2. 2. 获得登录token
  3. 3. 获得RSAkey和Pubkey
  4. 4. 提交登录请求
  5. 5. 检查是否登录成功

最近在做一个项目需要下载大量数据,因为下载过程中需要登录百度账号才能获得数据访问权限,所以需要使用程序模拟浏览器的账号登录行为,本人Java党,果断用HttpClient包,HttpClient是一个支持HTTP协议Java语言的开源工具包,可以通过它模拟各种浏览器的请求,相当于一个没有界面的浏览器。本文首先使用FireFox浏览器的FireBug插件监控登录过程的各个请求和参数生成过程,再使用HttpClient对参数进行封装并发送请求,最终实现登录。整个过程感谢CSDN中@crazylin007分享JS密码加密代码。


初始化

登录过程主要有四步,分别是初始化,接着是获得本次登录的token,再是获得rsakey和Pubkey,最后是将登录参数一起通过Post请求发送给服务器。初始化过程对HttpClient进行初始化设置,创建HttpClient对象,设置其Cookies机制为浏览器模式,主要为如下代码。

HttpClient httpClient = new HttpClient();
httpClient.getParams().setCookiePolicy(org.apache.commons.httpclient.cookie.CookiePolicy.BROWSER_COMPATIBILITY);
httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0");  

接着是加载百度页面,获得首次访问页面的Cookie,具体代码如下:

String url = "https://passport.baidu.com/v2/?login";
HttpMethod getMethod = new GetMethod(url);
try
{
    httpClient .executeMethod(getMethod);
} 
catch (HttpException e)
{
    // TODO Auto-generated catch block
    e.printStackTrace();
}
catch (IOException e)
{
    // TODO Auto-generated catch block
    e.printStackTrace();
}finally
{
    getMethod.releaseConnection();
}

获得登录token

token相当于该次登录的ID,点击登录时会发送请求获得token,token值由服务器给定,在FireBug中可以看到如图所示的Get请求。
获得token的Get请求
可以看到该Get请求一共有8个参数,其中tt表示当前时间,gid是随机GUID,可通过如下JS代码生成,Java可直接使用JS引擎调用JS函数,具体方法请百度之。其它参数直接按照图示给定即可。

function guidRandom() {
return "xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    var r = Math.random() * 16 | 0,
    v = c == "x" ? r : (r & 3 | 8);
    return v.toString(16)
}).toUpperCase()
}

知道了Get请求中每个参数的值后便可通过HttpClient模拟发送Get请求了,请求的响应里包括一段JSON格式的数据,将其中的token解析出来就OK了,具体代码如下。

/**
 * 百度登录先获得Token
 * @param gid 随机ID
 * @return
 */
public String getToken(HttpClient client, String gid)
{
    String tt = "" + (new Date()).getTime();
    String baseUrl = "https://passport.baidu.com/v2/api/?getapi&tpl=dev&apiver=v3&tt=%s&class=login&gid=%s&logintype=dialogLogin&callback=bd__cbs__bsbrf9";
    String requestUrl = String.format(baseUrl, tt, gid);
    HttpMethod getMethod = new GetMethod(requestUrl);
    try
    {
        client.executeMethod(getMethod);
        if(!(getMethod.getStatusCode() == HttpStatus.SC_OK)) return "";
        String jsonStr = getMethod.getResponseBodyAsString();
        jsonStr = jsonStr.substring(jsonStr.indexOf('(') + 1, jsonStr.lastIndexOf(')'));

        JSONObject jsonObj = new JSONObject(jsonStr);
        String token = new JSONObject(jsonObj.getString("data")).getString("token").toString();
        return token;
    } 
    catch (HttpException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JSONException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }finally
    {
        getMethod.releaseConnection();
    }

    return "";
}

获得RSAkey和Pubkey

在登录框输入用户名并将鼠标点击到密码框后可以看到会发送另外一条Get请求,该请求如下图所示。
获得RSAkey和Pubkey的请求
可以看到该请求一共有5个参数,其中token即为上文解析出来的,gid和上文一样,tt也是当前时间,剩余参数直接按照图中给定即可。下面便可以通过HttpClient执行该Get请求,其返回的响应字符串可解析出JSON格式的数据,包括rsakey和pubkey字段,具体代码如下。

/**
 * 百度登录获得RSA加密公钥和Pubkey
 * @param client
 * @param gid
 * @param token
 * @return
 */
public String[] getRSAKeyAndPubKey(HttpClient client, String gid, String token)
{
    String tt = "" + (new Date()).getTime();
    String baseUrl = "https://passport.baidu.com/v2/getpublickey?token=%s&tpl=dev&apiver=v3&tt=%s&gid=%s&callback=bd__cbs__fbzosu";
    String requestUrl = String.format(baseUrl, token, tt, gid);
    HttpMethod getMethod = new GetMethod(requestUrl);
    try
    {
        client.executeMethod(getMethod);
        if(!(getMethod.getStatusCode() == HttpStatus.SC_OK)) return null;
        String jsonStr = getMethod.getResponseBodyAsString();
        jsonStr = jsonStr.substring(jsonStr.indexOf('(') + 1, jsonStr.lastIndexOf(')'));

        JSONObject jsonObj = new JSONObject(jsonStr);
        String rsaKey = jsonObj.getString("key");
        String pubKey = jsonObj.getString("pubkey");

        return new String[]{rsaKey, pubKey};
    } catch (HttpException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }catch (IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JSONException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }finally
    {
        getMethod.releaseConnection();
    }
    return null;
}

提交登录请求

获得了RSAkey和Pubkey便可通过RSA加密获得密码加密后的密文,并将其作为参数发送给百度服务器了,最终提交登录请求的是Post请求,通过FireBug可得到如下图所示的请求。

登录POST请求
POST主要的参数为password,是由给定Pubkey通过RSA加密得到的密文,rsakey为上文获得的rsakey,tt仍然为当前时间,gid同上文,其它参数按照图示给定即可。使用HttpClient提交此POST请求的代码如下所示。

/**
 * 百度账号登录
 * @return
 */
public HttpClient baiduLogin()
{
    HttpClient httpClient = initHttpClient();

    loadPage(httpClient);

    String gid = baiduEncoder.randomGuid();
    Map<String, String> parameters = new HashMap<String, String>();

    parameters.put("gid", gid);

    String token = getToken(httpClient, gid);
    parameters.put("token", token);

    String [] rsa = getRSAKeyAndPubKey(httpClient, gid, token);
    if(rsa == null) return null;
    parameters.put("rsakey", rsa[0]);
    parameters.put("pubkey", rsa[1]);
    parameters.put("username", userName);
    parameters.put("password", password);

    String requestUrl= "https://passport.baidu.com/v2/api/?login";
    PostMethod loginMethod = new PostMethod(requestUrl);
    wrapBaiduLoginPostParameters(parameters, loginMethod);

    try
    {
        //发送登录请求
        httpClient.executeMethod(loginMethod);
        //check login result
        boolean loginResult = checkBaiduLoginResult(httpClient);

        if(loginResult)
        {
            System.out.println(String.format("恭喜你百度账号%s,登录成功!", userName));
        }
        else
        {
            System.out.println(String.format("对不起%s,登录失败!", userName));
            return null;
        }
    }
    catch (HttpException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
        return null;
    } 
    catch (IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
        return null;
    }
    catch (Exception e)
    {
        e.printStackTrace();
        return null;
    }
    finally
    {
        loginMethod.releaseConnection();
    }

    return httpClient;
}
/**
 * 封装登录百度账号的参数
 * @param parameters
 * @param postMethod
 */
public void wrapBaiduLoginPostParameters(Map<String, String> parameters, PostMethod postMethod)
{
    List<NameValuePair> parameterList = new ArrayList<NameValuePair>();
    parameterList.add(new NameValuePair("apiver", "v3"));
    parameterList.add(new NameValuePair("callback", "parent.bd__pcbs__rojm4c"/*"parent.bd__pcbs__avv1a8"*/));
    parameterList.add(new NameValuePair("charset", "UTF-8"));
    parameterList.add(new NameValuePair("codestring", ""));
    parameterList.add(new NameValuePair("countrycode", ""));
    parameterList.add(new NameValuePair("crypttype", "12"));
    parameterList.add(new NameValuePair("detect", "1"));
    parameterList.add(new NameValuePair("gid", parameters.get("gid")));
    parameterList.add(new NameValuePair("idc", ""));
    parameterList.add(new NameValuePair("isphone", ""));
    parameterList.add(new NameValuePair("logLoginType", "pc_loginDialog"));
    parameterList.add(new NameValuePair("loginmerge", "true"));
    parameterList.add(new NameValuePair("logintype", "dialogLogin"));
    parameterList.add(new NameValuePair("mem_pass", "on"));

    String pubKey = parameters.get("pubkey");
    String pwd = parameters.get("password");
    String password = baiduEncoder.encryptRSAPassword(pwd, pubKey);
    parameterList.add(new NameValuePair("password", password));


    parameterList.add(new NameValuePair("ppui_logintime", "12306"/*"38914272"*/));
    parameterList.add(new NameValuePair("quick_user", "0"));
    String rsaKey = parameters.get("rsakey");
    parameterList.add(new NameValuePair("rsakey", rsaKey));
    parameterList.add(new NameValuePair("safeflg", "0"));
    parameterList.add(new NameValuePair("splogin", "rate"));
    parameterList.add(new NameValuePair("staticpage", "http://developer.baidu.com/static/developer3/html/v3Jump.html"));
    parameterList.add(new NameValuePair("subpro", ""));
    String token = parameters.get("token");
    parameterList.add(new NameValuePair("token", token));
    parameterList.add(new NameValuePair("tpl", "dev"));
    String tt = (new Date()).getTime() + "";
    parameterList.add(new NameValuePair("tt", tt));
    parameterList.add(new NameValuePair("u", "http://openapi.baidu.com/"));
    parameterList.add(new NameValuePair("username", parameters.get("username")));
    parameterList.add(new NameValuePair("varifycode", ""));


    NameValuePair [] nameValuePairArray = new NameValuePair[parameterList.size()];
    parameterList.toArray(nameValuePairArray);

    postMethod.setRequestBody(nameValuePairArray);


}

检查是否登录成功

账号若登录成功在HttpClient中会有相应的cookie记录,如下代码所示,至此整个过程就结束啦。

/**
 * 通过Cookies检查百度账号是否登录成功
 * @param client
 * @return
 */
public boolean checkBaiduLoginResult(HttpClient client)
{
    Cookie[] cookies = client.getState().getCookies();
    if(cookies != null)
    {
        for (Cookie cookie: cookies)
            if(cookie.getName().equals("BDUSS")) return true;
    }
    return false;
}
文章目录
  1. 1. 初始化
  2. 2. 获得登录token
  3. 3. 获得RSAkey和Pubkey
  4. 4. 提交登录请求
  5. 5. 检查是否登录成功