Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

Posted cjyong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

 

以上是关于Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)的主要内容,如果未能解决你的问题,请参考以下文章

通过抓包,实现Python模拟登陆各网站,原理分析!

如何用fiddler模拟网络环境

Android App 如何防止抓包

Android弹幕实现:基于B站弹幕开源系统

关于网络抓包的一些记录

Fiddler抓包使用教程-模拟低速网络环境