Android網絡開發實例(基於抓包實現的網絡模擬登錄,登出和強制登出)


學習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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM