上课的时候经常忘记教室,打开教务系统看又太麻烦,所以最近想做一个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);
}
}
}