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

莫道桑榆晚,为霞尚满天。这篇文章主要讲述Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)相关的知识,希望能为你提供帮助。
学习android有几个月了,最近喜欢上了网络编程,于是想通过Android写一些一个小程序用于连接外网.在这里非常感谢雪夜圣诞的支持,非常感谢,给我打开新的一扇门.
1.声明,本程序只能用于西南大学连接外网登录,其他网站需要自己进行抓包测试.
2.声明,本文更多的是关注网络抓包已经,本地构造,如果有什么错误,请尽情指教,非常感谢.
3.声明,最后源代码,以全部上传github,需要的同志可以自行下载,文章结尾会附带链接.
废话不多说,正文开始:
学校官网

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

文章图片

第一步,首先需要实现的是登录操作:
当我们点击登录外网会出现以下页面:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

这个页面时关键,我们就要在这个页面进行抓包处理.我使用的是chorme浏览器,我打开chrome浏览器的开发者工具,从中选择network进行信息监控以下界面:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

这里需要关注的是,我们需要勾选上Preserve log,这样页面跳转时,发送的信息就不会消失了.然后,我们点击连接按钮,我们我可以发现以下情况:
【Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)】
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

 
 
其实我们可以发现我们要实现登录按钮,我们需要使用的url就是第一个,我们点击第一个url查看数据包详情,这样子我们就可以知道这个url需要哪些数据:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

这里我们可以发现,其实浏览器是向这个url发送了一个post请求,在post中放置了如下数据(userId,password,service,queryString,operatroPwd,operatorUserId,validcode).
我们很容易就发现userId和passwordId(就是账号和密码),service经过我多次测试并不会改变,应该是固定值,除了queryString之外的属性都是空的.难点就在这个queryString,我们点击view source查看原来编码(这里需要特别注意,浏览器会进行一次编码显示给我们,我们使用的应该是source原来的value值)
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

我们可以发现,其实两者的内容都是一样的,就是=编码的格式不同而已,因此我们只要向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="https://www.songbingjia.com/android/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 = https://www.songbingjia.com/android/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进行信息的交流,比较有代表性)
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

 
在学校网络管理中心,有校园网推出选项,当我们点击时,会出现如下情况:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

这说明,强制下线并不是单纯向一个url发送一个链接就可以完成的,在这里我们就需要进行抓包处理:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

这里我们可以发现,这里发送的请求也很简单,就是向login_judge.jsf发送一个post请求,post中数据也很简单,就两个内容(name,password)
然后我们点击我的设备,就可以进行退出网络操作了.我们截取一下包:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

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

文章图片

我们可以发现,这里需要向userself_ajax.jsf?methodName=xxxxx,发送一个post请求,其中数据特别简单一个key和一串数字,并没有用户名和密码,而Cookie中出现了,很明显2个页面之间的交流是通过cookie来是实现的,所以我们需要在登录的页面获取对应的cookie进行编辑,向这个url发送post请求.难点在于,封装的第二个数据是什么?这里就要进行苦逼的网页代码查询了,我们点开onlinedevice_list.jsf进行代码查询: (由于网页代码太长了,我截取一部分有用的进行分享)
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

我们可以发现的第二个数据,其实就是我们不同设备的局域网ip地址,这样子,数据也获取到了,cookie也得到了,我们只要向指定url发送post请求就可以了.
这里贴一下强制下线函数的代码:
//进行强制下线操作 private boolean forceLogoutValidate(String username,String passwd) throws Exception { //构造填充参数 String data ="https://www.songbingjia.com/android/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 = "https://www.songbingjia.com/android/key="+username+":" +m.group(1); HttpUtil.sendPostRequest(myurl, mydata, true, Cookie, "utf-8"); } return true; }

 
到这里,所有的重要的函数和抓包方法都已经讲解完毕,最后贴一下手机APP的截图:
Android网络开发实例(基于抓包实现的网络模拟登录,登出和强制登出)

文章图片

贴一下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="https://www.songbingjia.com/android/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 ="https://www.songbingjia.com/android/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 = "https://www.songbingjia.com/android/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
 

    推荐阅读