學習Android有幾個月了,最近喜歡上了網絡編程,於是想通過Android寫一些一個小程序用於連接外網.在這里非常感謝雪夜聖誕的支持,非常感謝,給我打開新的一扇門.
1.聲明,本程序只能用於西南大學連接外網登錄,其他網站需要自己進行抓包測試.
2.聲明,本文更多的是關注網絡抓包已經,本地構造,如果有什么錯誤,請盡情指教,非常感謝.
3.聲明,最后源代碼,以全部上傳github,需要的同志可以自行下載,文章結尾會附帶鏈接.
廢話不多說,正文開始:
學校官網

第一步,首先需要實現的是登錄操作:
當我們點擊登錄外網會出現以下頁面:

這個頁面時關鍵,我們就要在這個頁面進行抓包處理.我使用的是chorme瀏覽器,我打開chrome瀏覽器的開發者工具,從中選擇network進行信息監控以下界面:

這里需要關注的是,我們需要勾選上Preserve log,這樣頁面跳轉時,發送的信息就不會消失了.然后,我們點擊連接按鈕,我們我可以發現以下情況:

其實我們可以發現我們要實現登錄按鈕,我們需要使用的url就是第一個,我們點擊第一個url查看數據包詳情,這樣子我們就可以知道這個url需要哪些數據:

這里我們可以發現,其實瀏覽器是向這個url發送了一個post請求,在post中放置了如下數據(userId,password,service,queryString,operatroPwd,operatorUserId,validcode).
我們很容易就發現userId和passwordId(就是賬號和密碼),service經過我多次測試並不會改變,應該是固定值,除了queryString之外的屬性都是空的.難點就在這個queryString,我們點擊view source查看原來編碼(這里需要特別注意,瀏覽器會進行一次編碼顯示給我們,我們使用的應該是source原來的value值)

我們可以發現,其實兩者的內容都是一樣的,就是=編碼的格式不同而已,因此我們只要向http://222.198.127.170/發送一個get請求,然后把對應的內容截取出來就可以了.
因此登錄很簡單了,網址有了,填充的數據也知道了,我只要發送一個post請求就可以實現登錄功能了.這里貼一下登錄函數的代碼
//進行登錄操作 private boolean loginValidate(String username,String passwd) throws Exception { final String html = HttpUtil.sendGetRequest("http://222.198.127.170/", false, null, "gbk"); //使用正則表達式獲取對應的填充數據 String p = "jsp\\?(.+?)'</script>"; Pattern reg = Pattern.compile(p); Matcher m= reg.matcher(html); String FillingStr = ""; if(m.find()) { FillingStr = m.group(1); } //這里需要注意,需要使用utf-8格式進行編碼 FillingStr = URLEncoder.encode(FillingStr,"utf-8"); final String url = "http://222.198.127.170/eportal/InterFace.do?method=login"; final String data="userId="+username+"&password="+passwd+"&service=%25E9%25BB%2598%25E8%25AE%25A4&queryString="+FillingStr+"&operatorPwd=&operatorUserId=&validcode="; //發送登錄請求 String html2=HttpUtil.sendPostRequest(url, data, false, null, "gbk"); if(html2.contains("success")) return true; return false; }
HttpUtil是我自己寫的一個發送Http請求的工具類,我把工具類列出來,github中有源碼,需要的可以進行查閱.
package com.network.cjyong.networklogin.util; /** * Created by cjyong on 2017/3/5. */ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class HttpUtil { /** * 向對應的網址發送get請求,以String的形式返回服務器的相應 * * @author cjyong at 2017/3/5 * @param url 發送請求的網址 * @param usecookie 是否使用cookie * @param cookie 需要攜帶的cookie * @param encoding 編碼格式 * @return 以string的形式返回服務器的響應 * @throws Exception */ public static String sendGetRequest(final String url,final boolean usecookie,final String cookie,final String encoding) throws Exception { FutureTask<String> task = new FutureTask<String>( new Callable<String>() { @Override public String call() throws Exception { URL turl = new URL(url); HttpURLConnection conn = (HttpURLConnection) turl.openConnection(); //設置時間限制,拋出異常 conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); if(usecookie) conn.setRequestProperty("Cookie", cookie); InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is,encoding)); StringBuilder sb = new StringBuilder(); String line = null; while((line = reader.readLine())!= null) sb.append(line+"\n"); return sb.toString(); } }); //格外進行一個線程進行網絡操作,防止堵塞 new Thread(task).start(); return task.get(); } /** * 向對應的網址發送post請求,以String的形式返回服務器的相應 * * @author cjyong at 2017/3/5 * @param url 發送請求的網址 * @param data 發送post請求攜帶的數據 * @param usecookie 是否使用cookie * @param cookie 需要攜帶的cookie * @param encoding 編碼格式 * @return 以string的形式返回服務器的響應 * @throws Exception */ public static String sendPostRequest(final String url,final String data,final boolean usecookie,final String cookie,final String encoding) throws Exception { FutureTask<String> task = new FutureTask<String>( new Callable<String>() { @Override public String call() throws Exception { URL turl = new URL(url); HttpURLConnection conn = (HttpURLConnection) turl.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); //設置時間限制,拋出異常 conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); if(usecookie) conn.setRequestProperty("Cookie", cookie); OutputStream outStream = conn.getOutputStream(); outStream.write(data.getBytes()); outStream.flush(); outStream.close(); InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is,encoding)); StringBuilder sb = new StringBuilder(); String line = null; while((line = reader.readLine())!= null) sb.append(line+"\n"); return sb.toString(); } }); //格外進行一個線程進行網絡操作,防止堵塞 new Thread(task).start(); return task.get(); } /** * 向對應的網址發送post請求,獲取對應的cookie,以備后用 * * @author cjyong at 2017/3/5 * @param url 發送請求的網址 * @param data 發送post請求攜帶的數據 * @return 以string的形式返回服務器的響應 * @throws Exception */ public static String getCookie(final String url,final String data)throws Exception { FutureTask<String> task = new FutureTask<String>( new Callable<String>() { @Override public String call() throws Exception { byte[] Data = data.getBytes(); URL turl=new URL(url); HttpURLConnection conn = (HttpURLConnection)turl.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); //設置連接與讀取時間過期返回異常 conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); OutputStream outStream = conn.getOutputStream(); outStream.write(Data); outStream.flush(); outStream.close(); String Cookie=conn.getHeaderField("Set-Cookie"); return Cookie; } } ); //格外進行一個線程進行網絡操作,防止堵塞 new Thread(task).start(); return task.get(); } }
登出功能類似,我這里就不在贅述了,貼一下登出函數的代碼:
//進行登出操作 private boolean logoutValidate() throws Exception { String html = HttpUtil.sendGetRequest("http://222.198.127.170/eportal/InterFace.do?method=logout",false,null,"utf-8"); if(html.contains("success")) return true; return false; }
最后來講一下,強制下線功能的實現(這個需要用到cookie進行信息的交流,比較有代表性)

在學校網絡管理中心,有校園網推出選項,當我們點擊時,會出現如下情況:

這說明,強制下線並不是單純向一個url發送一個鏈接就可以完成的,在這里我們就需要進行抓包處理:

這里我們可以發現,這里發送的請求也很簡單,就是向login_judge.jsf發送一個post請求,post中數據也很簡單,就兩個內容(name,password)
然后我們點擊我的設備,就可以進行退出網絡操作了.我們截取一下包:


我們可以發現,這里需要向userself_ajax.jsf?methodName=xxxxx,發送一個post請求,其中數據特別簡單一個key和一串數字,並沒有用戶名和密碼,而Cookie中出現了,很明顯2個頁面之間的交流是通過cookie來是實現的,所以我們需要在登錄的頁面獲取對應的cookie進行編輯,向這個url發送post請求.難點在於,封裝的第二個數據是什么?這里就要進行苦逼的網頁代碼查詢了,我們點開onlinedevice_list.jsf進行代碼查詢: (由於網頁代碼太長了,我截取一部分有用的進行分享)

我們可以發現的第二個數據,其實就是我們不同設備的局域網ip地址,這樣子,數據也獲取到了,cookie也得到了,我們只要向指定url發送post請求就可以了.
這里貼一下強制下線函數的代碼:
//進行強制下線操作 private boolean forceLogoutValidate(String username,String passwd) throws Exception { //構造填充參數 String data ="name="+username+"&password="+passwd; String url= "http://service2.swu.edu.cn/selfservice/module/scgroup/web/login_judge.jsf"; //構造cookie String Cookie=HttpUtil.getCookie(url,data); Cookie=String.format(Cookie+" rmbUser=true; userName=%s; passWord=%s; oldpassWord=%s;", username,passwd,passwd); String listurl= "http://service2.swu.edu.cn/selfservice/module/webcontent/web/onlinedevice_list.jsf"; String html= HttpUtil.sendGetRequest(listurl, true, Cookie, "gbk"); //賬號密碼錯誤 if(html.contains("您還未登錄或會話過期")) return false; //獲取設備的IP地址構造填充數據 String p = "<span id=\"a1\">IP : (.+?)</span >"; Pattern reg = Pattern.compile(p); Matcher m=reg.matcher(html); //將所有的設備進行下線 while(m.find()) { //執行下線操作 String myurl = "http://service2.swu.edu.cn/selfservice/module/userself/web/userself_ajax.jsf?methodName=indexBean.kickUserBySelfForAjax"; String mydata = "key= "+username+":" +m.group(1); HttpUtil.sendPostRequest(myurl, mydata, true, Cookie, "utf-8"); } return true; }
到這里,所有的重要的函數和抓包方法都已經講解完畢,最后貼一下手機APP的截圖:

貼一下MainActivity的代碼:
package com.network.cjyong.networklogin; import android.app.AlertDialog; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.net.URLEncoder; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.network.cjyong.networklogin.util.HttpUtil; public class MainActivity extends AppCompatActivity { EditText etUsername,etUserpass; Button login,cancel,logout,forceout,help; SharedPreferences preferences; SharedPreferences.Editor editor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //綁定組件 bindCompoent(); //程序初始化 init(); } //綁定各種主鍵並設置好監聽器 private void bindCompoent() { //綁定各類主鍵 etUsername = (EditText) findViewById(R.id.userEditText); etUserpass = (EditText) findViewById(R.id.pwdEditText); login = (Button) findViewById(R.id.bnLogin); cancel = (Button) findViewById(R.id.bnCancel); logout = (Button) findViewById(R.id.bnLogout); forceout = (Button) findViewById(R.id.bnForceLogout); help = (Button) findViewById(R.id.bnHelp); //給取消按鈕綁定監聽器 cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { etUsername.setText(null); etUserpass.setText(null); } }); //給登錄按鈕監聽器 login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //驗證是否連接網絡和輸入是否合理 if(validate()) { //發送登錄請求 String username = etUsername.getText().toString(); String userpasswd = etUserpass.getText().toString(); try { if(loginValidate(username,userpasswd)) { Toast.makeText(getApplicationContext(), "登錄成功",Toast.LENGTH_LONG). show(); login.setEnabled(false); logout.setEnabled(true); //將正確的賬號和密碼存儲到本地中去 save(); } else { Toast.makeText(getApplicationContext(), "登錄失敗,請檢查你的用戶名和密碼是否正確", Toast.LENGTH_SHORT). show(); } } catch (Exception e) { e.printStackTrace(); } } } }); //給登出按鈕設置監聽器 logout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //驗證是否連接網絡和輸入是否合理 if(validate()) { //發送登錄請求 String username = etUsername.getText().toString(); String userpasswd = etUserpass.getText().toString(); try { if(logoutValidate()) { Toast.makeText(getApplicationContext(), "登出成功", Toast.LENGTH_SHORT). show(); logout.setEnabled(false); login.setEnabled(true); } else { Toast.makeText(getApplicationContext(), "登出失敗,請檢查你的用戶名和密碼是否正確", Toast.LENGTH_SHORT). show(); } } catch (Exception e) { e.printStackTrace(); } } } }); //給強制登出按鈕設置監聽器 forceout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //驗證是否連接網絡和輸入是否合理 if(validate()) { //發送登錄請求 String username = etUsername.getText().toString(); String userpasswd = etUserpass.getText().toString(); try { if(forceLogoutValidate(username,userpasswd)) { Toast.makeText(getApplicationContext(), "下線成功", Toast.LENGTH_SHORT). show(); logout.setEnabled(false); login.setEnabled(true); } else { Toast.makeText(getApplicationContext(), "下線失敗,請檢查你的用戶名和密碼是否正確", Toast.LENGTH_SHORT). show(); } } catch (Exception e) { e.printStackTrace(); } } } }); help.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(MainActivity.this). setTitle("幫助界面"). setMessage("本軟件適合在西南大學一鍵登錄外網 \n--------by cjyong\n" + "強制退出按鈕會強制退出所有當前賬號登錄的設備,請謹慎使用\n"+ "如果軟件有問題,請聯系QQ2686600303\n"). setNegativeButton("取消",null). show(); } }); } private void init() { //判斷以前是否登錄過,通過存儲在本地的記錄進行判斷 isOldUser(); //判斷網絡是否連接正確,是否可以聯網 wifiIsGood(); } //檢查網絡是否可以正確連接 private void wifiIsGood() { //判斷wifi是否連接 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo.State wifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState(); if(wifi== NetworkInfo.State.DISCONNECTED) { Toast.makeText(this,"請連接好WiFi再進行登錄",Toast.LENGTH_SHORT).show(); return ; } //判斷是否可以上網 try { //前往bibili網站 String html = HttpUtil.sendGetRequest("http://www.bilibili.com", false, null, "utf-8"); if(html.contains("http://222.198.127.170/")) { return; } else { Toast.makeText(this,"你的WiFi已經可以上網,不用登陸",Toast.LENGTH_LONG).show(); return; } } catch (Exception e) { e.printStackTrace(); } } //通過遍歷本地記錄,進行判斷是否之前登錄過,如果登錄自動填充兩個對話框 private void isOldUser() { //這里通過preference進行儲存相應數據 preferences = getSharedPreferences("userpass",0); editor = preferences.edit(); String username = preferences.getString("username", null); String userpass = preferences.getString("userpass",null); if(username != null && userpass!= null) { etUsername.setText(username); etUserpass.setText(userpass); } return; } //檢查wifi狀況和輸入情況 private boolean validate() { String username = etUsername.getText().toString(); String userpass = etUserpass.getText().toString(); if(username.equals("") || userpass.equals("")) { Toast.makeText(this,"用戶名或者密碼不可以為空,請重新輸入",Toast.LENGTH_SHORT).show(); return false; } return true; } //將正確的賬號和密碼存儲到本地中去 private void save() { String username = etUsername.getText().toString(); String userpass = etUserpass.getText().toString(); editor.putString("username",username); editor.putString("userpass",userpass); editor.commit(); } //進行登錄操作 private boolean loginValidate(String username,String passwd) throws Exception { final String html = HttpUtil.sendGetRequest("http://222.198.127.170/", false, null, "gbk"); //使用正則表達式獲取對應的填充數據 String p = "jsp\\?(.+?)'</script>"; Pattern reg = Pattern.compile(p); Matcher m= reg.matcher(html); String FillingStr = ""; if(m.find()) { FillingStr = m.group(1); } //這里需要注意,需要使用utf-8格式進行編碼 FillingStr = URLEncoder.encode(FillingStr,"utf-8"); final String url = "http://222.198.127.170/eportal/InterFace.do?method=login"; final String data="userId="+username+"&password="+passwd+"&service=%25E9%25BB%2598%25E8%25AE%25A4&queryString="+FillingStr+"&operatorPwd=&operatorUserId=&validcode="; //發送登錄請求 String html2=HttpUtil.sendPostRequest(url, data, false, null, "gbk"); if(html2.contains("success")) return true; return false; } //進行登出操作 private boolean logoutValidate() throws Exception { String html = HttpUtil.sendGetRequest("http://222.198.127.170/eportal/InterFace.do?method=logout",false,null,"utf-8"); if(html.contains("success")) return true; return false; } //進行強制下線操作 private boolean forceLogoutValidate(String username,String passwd) throws Exception { //構造填充參數 String data ="name="+username+"&password="+passwd; String url= "http://service2.swu.edu.cn/selfservice/module/scgroup/web/login_judge.jsf"; //構造cookie String Cookie=HttpUtil.getCookie(url,data); Cookie=String.format(Cookie+" rmbUser=true; userName=%s; passWord=%s; oldpassWord=%s;", username,passwd,passwd); String listurl= "http://service2.swu.edu.cn/selfservice/module/webcontent/web/onlinedevice_list.jsf"; String html= HttpUtil.sendGetRequest(listurl, true, Cookie, "gbk"); //賬號密碼錯誤 if(html.contains("您還未登錄或會話過期")) return false; //獲取設備的IP地址構造填充數據 String p = "<span id=\"a1\">IP : (.+?)</span >"; Pattern reg = Pattern.compile(p); Matcher m=reg.matcher(html); //將所有的設備進行下線 while(m.find()) { //執行下線操作 String myurl = "http://service2.swu.edu.cn/selfservice/module/userself/web/userself_ajax.jsf?methodName=indexBean.kickUserBySelfForAjax"; String mydata = "key= "+username+":" +m.group(1); HttpUtil.sendPostRequest(myurl, mydata, true, Cookie, "utf-8"); } return true; } }
貼一下github地址(歡迎補充):
https://github.com/cai123nb/NetworkLogin/tree/master/main
講點廢話,其實我們可以看出,編碼並不困難,困難的使我們怎么抓取准確的網址和數據包,怎么填充正確的數據包.
只要我們這一點學習的好的話,,我們可以拓展開來,抓手機號碼的歸屬地,郵件/快遞的送達地址等,都是可以的.
第二,其實我的HttpUtil有點過時了,現在大多數的人都是使用HttpClient,因為HttpClient支持https,我用老版的用順手,也就沒有換了,如果
有人有不同的思路歡迎補充.在這里,拋磚引玉了,你我共勉.
非常感謝,閱讀.
17:30:14
