同步发表于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); }
基本上每一个细节点都变了,因此须要正对登录环节从新分析,分析出每个所需的元素后想办法模拟出来。