同步發表於http://avenwu.net/2015/04/12/cnblogs_login_fix/javascript
4月以來博客園悄然修改了登錄的接口,致使我等屁民開發的客戶端生生登錄不了。趁着週末從新對登錄進行了抓包分析,總算搞定,能夠歇一口氣:)html
截止目前登錄頁面地址是這樣的http://passport.cnblogs.com/user/signin?ReturnUrl=http%3A%2F%2Fwww.cnblogs.com%2Fjava
眼尖的園友應該發現了第一個變化,即登錄地址成了use/signin。固然確定不止這一出修改。android
以前登錄採用的是表單提交,如今登錄請求採用了ajax,利用post方式將加密後的用戶名,密碼拼接成json串發給服務器。git
我是怎麼知道的,固然不是猜的,web
詳細的js代碼能夠直接看到:ajax
var encrypt = new JSEncrypt(); encrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp0wHYbg/NOPO3nzMD3dndwS0MccuMeXCHgVlGOoYyFwLdS24Im2e7YyhB0wrUsyYf0/nhzCzBK8ZC9eCWqd0aHbdgOQT6CuFQBMjbyGYvlVYU2ZP7kG9Ft6YV6oc9ambuO7nPZh+bvXH0zDKfi02prknrScAKC0XhadTHT3Al0QIDAQAB'); var encrypted_input1 = encrypt.encrypt($('#input1').val()); var encrypted_input2 = encrypt.encrypt($('#input2').val()); var ajax_data = { input1: encrypted_input1, input2: encrypted_input2, remember: $('#remember_me').prop('checked') }; if(enable_captcha){ var captchaObj = $("#captcha_code_input").get(0).Captcha; ajax_data.captchaId = captchaObj.Id; ajax_data.captchaInstanceId = captchaObj.InstanceId; ajax_data.captchaUserInput = $("#captcha_code_input").val(); } is_in_progress = true; $.ajax({ url: ajax_url, type: 'post', data: JSON.stringify(ajax_data), contentType: 'application/json; charset=utf-8', dataType: 'json', headers: { 'VerificationToken': 'cZ0PISksjsWGEbj4IhANzxSXoXmLr9zNWVBzTNuy5khrwm0akh5Eo9XTrmoHt_RzFOKbWD2jOaibj7r_bZZlPLAx81c1:G8OVJmEYy2z1FJJUwvvy_mS3HLR-AYitaaf3eCXHUI8LIwAPjxnpkXqgR32zqSRli_gid77jDtaUlOjGgob8TjdIOq41' }, success: function (data) { if (data.success) { $('#tip_btn').html('登陸成功,正在重定向...'); location.href = return_url; } else { $('#tip_btn').html(data.message + "<br/><br/>聯繫 contact@cnblogs.com"); is_in_progress = false; if(enable_captcha) { captchaObj.ReloadImage(); } } }, error: function (xhr) { is_in_progress = false; $('#tip_btn').html('抱歉!出錯!聯繫 contact@cnblogs.com'); } });
雖然不是很懂js,可是這並不妨礙分析,這裏利用了裏面的加密函數,只知道大體用的是RSA加密,公鑰的話js中已經給出了,直接拿來用就能夠。原本想用java實現,加密函數內容太多,實現不容易,因此仍然用的這段原生的js加密、解密函數;json
如今java中js庫須要解決,查了下相關資料Moliza出了一個引擎Rhino,文檔到是不少,執行一個簡單的js語句應該沒問題,但這裏須要導入整個js庫文件,沒找的簡單可行的方法,只能迂迴前進,直接用weview來加密,寫一段js代碼,調用上面提到的js加密庫,效果也不錯,能夠正常解析:服務器
private final static String ENCRYPT = "javascript:encryptLoginInfo('%s','%s')"; public void login() { WebView webview = new WebView(mContext); webview.getSettings().setJavaScriptEnabled(true); webview.addJavascriptInterface(new Android(), "android"); webview.loadUrl(PAGE); webview.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { Logger.d("HTML URL=" + url); if (PAGE.equals(url) && !isLogining) { view.loadUrl(String.format(ENCRYPT, mListener.setName(), mListener.setPassword())); } } }); }
除了用戶數據加密外,這裏還須要添加一個頭部VerificationToken,僞造是不可能了,只能訪問登錄頁,利用正則匹配出來。app
Document doc = Jsoup.parse(html); String url = doc.select("#c_login_logincaptcha_CaptchaImage").attr("src"); if (!TextUtils.isEmpty(url)) { Logger.d("skip auto login as captcha is needed"); return ""; } Element script = doc.select("script").get(2); Pattern p = Pattern.compile("(?is)'VerificationToken': '(.+?)'"); // Regex for the value of the key Matcher m = p.matcher(script.html()); // you have to use html here and NOT text! Text will drop the 'key' part String VerificationToken; if (m.find()) { VerificationToken = m.group(1); } else { return ""; }
同理夜間的驗證碼登錄也須要調整,由於這個也變了,但原理是同樣的,分析html內的標籤找到驗證碼所在的img標籤,獲取src即圖片生成地址。
Document doc = Jsoup.parse(html); String url = doc.select("#LoginCaptcha_CaptchaImage").attr("src"); if (TextUtils.isDigitsOnly(url)) { return params; } //BotDetect.Init('LoginCaptcha', '6c5b12a021d8460fa8bc87cdf96f0d80', 'captcha_code_input', true, true, true, true, 1200, 7200, 0, true); Matcher matcher = Pattern.compile("(?is)BotDetect.Init\\('LoginCaptcha', '(.+?)',").matcher(html); if (matcher.find()) { params.captchaInstanceId = matcher.group(1); }
基本上每一個細節點都變了,因此須要正對登錄環節從新分析,分析出每個所需的元素後想辦法模擬出來。