上课的时候经常忘记教室,打开教务系统看又太麻烦,所以最近想做一个app能够自动推送上课信息、上课教室,查成绩,如果可以的话再做一个一键评教。其他功能还在想。以前没接触过不用api做的,模拟登录整整折腾了一天半,慢慢做吧,毕竟我菜。
先上两张效果图,我登录了之后点击了课表信息源数据显示在UI上。
首先用Charles抓包,抓包之前要记得清除历史数据,否则cookie保存到本地了获取不到。以下是学校教务系统登录界面:
对从打开教务系统开始到登录成功抓包主要数据:
第一次GET请求拿到第一个Cookie的JSESSIONID(位于Set-Cookie字段):
第二次POST请求提交用户名、密码等数据,页面重定向。以下是POST请求需要提交的数据:
其它字段都是固定的,但这里有一个It字段需要获取,于是用Find功能查找这个字段的来源
发现在ResponseBody里。
第三次GET请求拿到重定向页面的Cookie,也就是JSESSIONID字段:
代码实现(草稿)
我先写好了登录界面的布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.asa.easyxiian.MainActivity"> <ImageView android:id="@+id/first_page_background" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/first_page" /> <LinearLayout android:id="@+id/log_in" android:layout_width="150dp" android:layout_height="match_parent" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:gravity="center" android:orientation="vertical"> <EditText android:id="@+id/user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="学号" /> <EditText android:id="@+id/password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="密码" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="LogIn" android:text="log in" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/user_name_display" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </ScrollView> <TextView android:id="@+id/password_display" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </RelativeLayout>
最下面的两个TextView是用来测试用户名密码的输入还有用来测试后来得到的Response的,之后会删掉换到其他Activity
NetworkUtil工具类,用来封装GET和POST请求,用的工具包是HttpClient和Jsoup。
package com.example.asa.easyxiian; import android.net.Uri; import android.util.Log; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.jsoup.Jsoup; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; /** * Created by asa on 2018/4/21. */ public class NetworkUtils { static HttpClient mClient; final static String QUERY_PARAM = "ticket"; private static final String BASE_URL = "http://jwxt.xidian.edu.cn/caslogin.jsp"; public static GetMethod getAction(HttpClient client, String url) throws IOException { mClient = client; GetMethod getMethod = new GetMethod(url); mClient.executeMethod(getMethod); return getMethod; } public static GetMethod getMethodUseCookie(HttpClient client, String JSESSION, String url) throws IOException { GetMethod getMethod = new GetMethod(url); getMethod.setFollowRedirects(false); getMethod.addRequestHeader(new Header("Cookie", "JSESSION=" + JSESSION)); client.executeMethod(getMethod); return getMethod; } public static String[] getData(GetMethod getMethod) throws IOException { String[] results = new String[2]; String JSESSION; org.jsoup.nodes.Document document; String lt = ""; InputStream inputStream = getMethod.getResponseBodyAsStream(); StringBuilder output = new StringBuilder(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "gbk"); BufferedReader reader = new BufferedReader(inputStreamReader); String line = reader.readLine(); while (line != null) { output.append(line); line = reader.readLine(); } document = Jsoup.parseBodyFragment(output.toString()); org.jsoup.nodes.Element body = document.body(); lt = body.select("[name=lt]").attr("value"); getMethod.releaseConnection(); JSESSION = getMethod.getResponseHeader("Set-Cookie").getValue().trim().split(";")[0].split(",")[1].trim().split("=")[1]; results[0] = JSESSION; results[1] = lt; return results; } public static PostMethod postAction(HttpClient client, String url, String userName, String password, String JSESSION, String lt) throws IOException, URIException { mClient = client; PostMethod postMethod = new PostMethod(); URI uri = new URI(url); postMethod.setURI(uri); postMethod.addRequestHeader(new Header("Cookie", "JSESSION=" + JSESSION)); postMethod.addParameter(new NameValuePair("_eventId", "submit")); postMethod.addParameter(new NameValuePair("username", userName)); postMethod.addParameter(new NameValuePair("password", password)); postMethod.addParameter(new NameValuePair("lt", lt)); postMethod.addParameter(new NameValuePair("execution", "e1s1")); postMethod.addParameter(new NameValuePair("rmShown", "1")); postMethod.addParameter(new NameValuePair("submit", "")); mClient.executeMethod(postMethod); return postMethod; } public static String buildUrl(String locationQuery) { Uri builtUri = Uri.parse(BASE_URL).buildUpon() .appendQueryParameter(QUERY_PARAM, locationQuery) .build(); URL url = null; try { url = new URL(builtUri.toString()); } catch (MalformedURLException e) { e.printStackTrace(); } Log.v("NetworkUtils : ", "Built URI " + url); return url.toString(); } }
下面是主程序代码,用了AsynTask,我知道用它实现不太好,以后再改,模拟登录的时候一定要关闭重定向,否则拿不到第二个JSESSIONID。登录过程中的状态码一次200,两次302,否则登录失败。登录成功后拿到源数据后显示在主页面上。
package com.example.asa.easyxiian; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import java.io.IOException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; public class MainActivity extends AppCompatActivity { final static String loginUrl1 = "http://ids.xidian.edu.cn/authserver/login?service=http%3A%2F%2Fjwxt.xidian.edu.cn%2Fcaslogin.jsp"; final static String classInforURL = "http://jwxt.xidian.edu.cn/xkAction.do?actionType=6"; private EditText mEditTextUserName; private EditText mEditTextPassWord; private String mUserName; private String mPassWord; HttpClient mClient; TextView mTextViewUserName; TextView mTextViewPassWord; private GetMethod mGetMethod; String mJSESSION; String mClassInfo; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextViewUserName = (TextView) findViewById(R.id.user_name_display); mTextViewPassWord = (TextView) findViewById(R.id.password_display); ImageView background = (ImageView) findViewById(R.id.first_page_background); background.setAlpha(80); } public void LogIn(View view) { mEditTextUserName = (EditText) findViewById(R.id.user_name); mUserName = mEditTextUserName.getText().toString(); mEditTextPassWord = (EditText) findViewById(R.id.password); mPassWord = mEditTextPassWord.getText().toString(); new LogInTask().execute(); new getClassesInformationTask().execute(classInforURL); } private class getClassesInformationTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... strings) { String result = null; GetMethod getMethod = null; try { getMethod = NetworkUtils.getMethodUseCookie(mClient, mJSESSION, strings[0]); result = getMethod.getResponseBodyAsString(); mClassInfo = result; } catch (IOException e) { Log.e("MainActivity", "使用Cookie的GET失败。"); } return result; } @Override protected void onPostExecute(String s) { mTextViewUserName.setText(s); } } private class LogInTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... strings) { mClient = new HttpClient(); String data[] = new String[2]; String result = null; //GET请求拿到JSESSIONID try { GetMethod getMethod = NetworkUtils.getAction(mClient, loginUrl1); data = NetworkUtils.getData(getMethod); getMethod.releaseConnection(); } catch (IOException e) { Log.e("MainActivity : ", "第一步GET请求失败"); } //用拿到的JSESSIONID填充POST网址 String postUrl = "http://ids.xidian.edu.cn/authserver/login;jsessionid=" + data[0] + "?service=http%3A%2F%2Fjwxt.xidian.edu.cn%2Fcaslogin.jsp"; String urlResponse = null; String JSESSIONID2 = null; //POST请求发送登录数据,拿到第二步网址urlResponse try { PostMethod postMethod = NetworkUtils.postAction(mClient, postUrl, mUserName, mPassWord, data[0], data[1]); urlResponse = postMethod.getResponseHeader("Location").toString().split(" ")[1]; } catch (IOException e) { Log.e("MainActivity : ", "第二步POST请求失败"); } String logInPage = null; //第二次GET请求进入登录界面,拿到第二个JSESSIONID try { GetMethod getMethod = new GetMethod(urlResponse); getMethod.setFollowRedirects(false); int statusCode = mClient.executeMethod(getMethod); JSESSIONID2 = getMethod.getResponseHeader("Set-Cookie").getValue().split(";")[0].trim().split("=")[1]; Log.e("Set-Cookie", JSESSIONID2 + ""); //检查是否登录成功 Log.e("Status code", "" + statusCode); logInPage = getMethod.getResponseHeader("Location").toString().split(" ")[1]; Log.e("Response page", "" + logInPage); } catch (IOException e) { Log.e("MainActivity : ", "第二步GET请求失败"); } //进入登录后页面 GetMethod getMethod = new GetMethod(logInPage); getMethod.setFollowRedirects(false); getMethod.addRequestHeader(new Header("Cookie", "JSESSION=" + JSESSIONID2)); try { mClient.executeMethod(getMethod); result = getMethod.getResponseBodyAsString(); mGetMethod = getMethod; mJSESSION = JSESSIONID2; } catch (IOException e) { Log.e("MainActivity : ", "进入登录后页面失败"); } return result; } @Override protected void onPostExecute(String s) { mTextViewUserName.setText(s); } } }