首先吐點苦水,作者的博客園—Android客戶端已經停止維護半年多了,明知登陸有問題但卻沒動力去改進,想使用MD規范+RxJava重構卻苦於接口問題,實在太不規范,而且還不穩定...
進入正題吧,第一種方法,JS注入登陸。
首先我們看看下圖博客園Web接口的登陸界面:簡簡單單,兩個輸入框,一個按鈕
檢查一下它的屬性:這里只看看密碼這個屬性,id="input2",用戶名id="input1",登陸的id="signin"
接下來就好辦了,寫一個這樣的布局,非常簡單:當然真正的開發界面不能這樣丑,而且webview應該隱藏起來。
獲取兩個EditText的輸入文本為String name,pwd之后,我們為webview注入用戶名密碼,並觸發登陸按鈕click:
默認加載這個頁面,也就是登陸頁面。
1 webview.loadUrl("https://passport.cnblogs.com/user/signin?ReturnUrl=http%3A%2F%2Fwww.cnblogs.com%2F");
2 String js = "javascript:document.getElementById('input1').setAttribute('value','" + name + "');" + "document.getElementById('input2').setAttribute('value','" + pwd + "');" + "document.getElementById('signin').click();"; 3 webview.loadUrl(js);
這樣就能登陸了。當然成功與否可以給webview添加client,然后在onPageFinished里面判斷,然后獲取cookie,這個cookie就能用來做各種用戶操作了。
第二種登陸方法:模擬登陸。第一種方法畢竟很繁瑣,沒什么技術含量,我們知道這個登陸接口是用到RSA加密的,只不過我們巧妙地避開了加密,直接拿到cookie而已。一起看看第二種加密方式吧:
說到模擬登陸,首先就離不開抓包。不知為何,去年還能使用fiddler抓到登陸接口,現在卻不行了,但是畢竟抓包工具這么多,還是有辦法的。我們就是使用IE+HttpWatch來看看吧
登陸界面的源碼是這樣的,很快就發現了公鑰publicKey以及它的加密流程了,對用戶名進行公鑰加密,對密碼進行公鑰加密,還有個remenber=true|false,這是ajax登陸的json數據

再往下看發現了VerificationToken這個頭部,起初我以為它有什么用,便寫個正則取出它的值:實際並沒什么用處
1 Pattern pattern = Pattern.compile("'VerificationToken'\\s*:\\s*'([^\\s\\n]+)'"); 2 Matcher matcher = pattern.matcher(s);
我們繼續抓包:輸入用戶名密碼之后,打開Record記錄,點擊登陸:看到以下的數據:登陸接口+POST json的數據 {input1:"RSA加密的用戶名",input2:"RSA加密的密碼",remenber:true}
切換到Headers看看里面的數據:

如我們期待一般,有VerificationToken和Ajax異步請求的頭部:X-Requested-With,還有兩個重要的header,Content-Type和Cookie,AspxAutoDetectCookieSupport=1是固定的,
SERVERID來自登陸界面,可自行獲取,至此,我們要模擬登陸的流程和數據都搞清楚了。下面是模擬登陸的代碼:使用AsyncHttpClient,沒必要Retrofit或者OKHttp了。
1 AsyncHttpClient client = new AsyncHttpClient(); 2 client.get("https://passport.cnblogs.com/user/signin?ReturnUrl=http%3A%2F%2Fwww.cnblogs.com%2F", new TextHttpResponseHandler() { 3 @Override 4 public void onFailure(int i, Header[] headers, String s, Throwable throwable) { 5 6 } 7 8 @Override 9 public void onSuccess(int i, Header[] headers, String s) { 10 String token = ""; 11 Pattern pattern = Pattern.compile("'VerificationToken'\\s*:\\s*'([^\\s\\n]+)'"); 12 Matcher matcher = pattern.matcher(s); 13 if (matcher.find()) { 14 token = matcher.group(1); 15 } 16 String tmpcookies = ""; 17 for (Header header : headers) { 18 String key = header.getName(); 19 String value = header.getValue(); 20 if (key.contains("Set-Cookie") && value.contains("SERVERID")) { 21 tmpcookies += value; 22 tmpcookies = tmpcookies.replace("Path=/", ""); 23 } 24 25 } 26 if (tmpcookies.length() > 0) { 27 tmpcookies = "AspxAutoDetectCookieSupport=1;" + tmpcookies.substring(0, tmpcookies.length() - 1); 28 } 29 LoginBean bean = new LoginBean();//只有三個屬性的Bean ,String類型的input1,input2,remenber boolean類型 30 bean.setRemember(true); 31 try { 32 bean.setInput1(RSA.encrypt("huanghaibin_dev", RSA.KEY));//公鑰加密 33 bean.setInput2(RSA.encrypt("11111111", RSA.KEY)); 34 String json = new Gson().toJson(bean); 35 ByteArrayEntity entity = null; 36 entity = new ByteArrayEntity(json.getBytes("UTF-8")); 38 client.addHeader("Cookie", tmpcookies); 39 client.addHeader("Content-Type", "application/json; charset=utf-8"); 40 //client.addHeader("VerificationToken", token); 41 client.addHeader("X-Requested-With", "XMLHttpRequest"); 42 client.post(WelcomeActivity.this, "https://passport.cnblogs.com/user/signin", entity, "application/json; charset=UTF-8", new TextHttpResponseHandler() { 43 @Override 44 public void onFailure(int i, Header[] headers, String s, Throwable throwable) { 45 46 } 47 48 @Override 49 public void onSuccess(int i, Header[] headers, String s) { 50 //返回{success:true}這種類型的json 51 } 52 }); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 } 57 });
RSA加密
1 public static PublicKey getPublicKey(String key) throws Exception { 2 X509EncodedKeySpec keySpec = new X509EncodedKeySpec( 3 Base64.decode(key.getBytes(),Base64.DEFAULT)); 4 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 5 PublicKey publicKey = keyFactory.generatePublic(keySpec); 6 return publicKey; 7 } 8 9 public static String encrypt(String source, String publicKey) 10 throws Exception { 11 Key key = getPublicKey(publicKey); 12 /** 得到Cipher對象來實現對源數據的RSA加密 */ 13 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 14 cipher.init(Cipher.ENCRYPT_MODE, key); 15 byte[] b = source.getBytes(); 16 /** 執行加密操作 */ 17 byte[] b1 = cipher.doFinal(b); 18 return new String(Base64.encode(b1,Base64.DEFAULT), 19 ConfigureEncryptAndDecrypt.CHAR_ENCODING); 20 }