师大+ 技术说明书 第五期:聊聊模拟登录

有些网站需要获取信息时,需要用户登录账户,这时就要用到模拟登录。

而目前的登录,大部分都是直接用post发送请求的,既然是这样,必然带有参数,原理也很简单,有参数,有url,那模拟在浏览器中的操作,就可以“骗”过网站实现登录的效果。

目前安全机制在不断进步,大部分登录操作都有重定向,比如【师大+】的图书馆和自习室的登录,当然,这一个重定向而已,也可以视为几乎没有任何安全的登录方式了,而类似github就会采用auth验证取得tooken等方式进行登录,明显是要安全很多的,但同时也会给模拟登录的实现造成很多困难。

当没有验证时,模拟登录是最简单的操作,一步到位,这里为了说明流程,直接选择这种网站,也是【师大+】中一个主要的登录源,江西师大的教务在线。

分析

写下模拟登录的代码之前,先来分析要登录的url,登录一次,抓包,看看会发送什么数据到服务器。

我习惯用火狐,在火狐中按F12即可打开网络调试。

输入教务在线用户名和密码后,点击登录,然后去网络调试找到第一个post的包,查看参数,如下图所示:

因为这是一个用Asp.net写的网页,所以发送的参数里面有点多,像__VIEWSTATE__EVENTVALIDATION之类的更是长长的一串,这两个字段是Asp.net网页特有的,稍微解释下:

  • __VIEWSTATE:控件的视图状态,用于在页面的多个版本之间维护控件相关(例如属性)的值。

  • __EVENTVALIDATION:ASP.NET 2.0的新增的安全措施。该功能可以阻止由潜在的恶意用户从浏览器端发送的未经授权的请求。

可能看完这两个解释有人就晕菜了,其实不用担心,这东西,照搬就行,如果你想看看__VIEWSTATE里面记录了什么信息,找个解码器就行,这里就顺便推荐一个我用过的(ViewStateHacker):http://www.freebuf.com/sectool/4993.html

下面看看登录中,就学生身份来说,就要求我们输入两个参数,分别是用户名和密码,分别是StuNumPassword,那么在客户端中只要求用户输入这两个信息就行,如【师大+】的登录界面:

所以小结一下,如果需要以学生身份登录教务在线,那么就需要学号(用户名)和密码两个参数,其它照搬。

实现

下面看看模拟登录在【师大+】里面的具体实现,先把主要代码放出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

/*主要代码开始*/

NetManageUtil.post(Urlconfig.Education_Login_URL)
.addTag(TAG)
.addParams("__EVENTTARGET", "")
.addParams("__EVENTARGUMENT", "")
.addParams("__LASTFOCUS", "")
.addParams("__VIEWSTATE", "/wEPDwUJNjk1MjA1...")//源代码太长,影响代码可读性,所以这里节选部分
.addParams("__EVENTVALIDATION", "/wEWSgLYl4...")//同上
.addParams("rblUserType", "Student")
.addParams("ddlCollege", "180 ")
.addParams("StuNum", getUsername(usernameET))
.addParams("TeaNum", "")
.addParams("Password", getPassword(passwordET))
.addParams("login", "登录")
.enqueue(new StringCallback() {
/*主要代码结束*/
@Override
public void onSuccess(String result, final Headers headers) {
EducationalSysLoginParse myParse = new EducationalSysLoginParse(result);
final List endList = myParse.getEndList();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (endList == null || endList.isEmpty()){
EventBus.getDefault().post(new EventModel<String>(EVENT.EDUCATION_LOGIN_SERVER_ERROR));
}else if (endList.get(0).toString().equals(EducationalSysLoginParse.LOGIN_FAIL_NO_ID_STR)) {
EventBus.getDefault().post(new EventModel<String>(EVENT.EDUCATION_LOGIN_FAILURE_NO_ID));
} else if (endList.get(0).toString().equals(EducationalSysLoginParse.LOGIN_FAIL_PASSWORD_INCORRECT_STR)) {
EventBus.getDefault().post(new EventModel<String>(EVENT.EDUCATION_LOGIN_FAILURE_PASSWORD_INCORRECT));
} else {
String userNum = getUsername(usernameET);
String userName = endList.get(0).toString();
String nowTerm = TermUtil.getNowTerm();
String baseCookie=null,specialCookie=null;
for (int i = 0; i < headers.size(); i++) {
if(headers.name(i).equals("Set-Cookie")){
baseCookie=cutBaseCookie(headers.value(i));
specialCookie=cutSpecialCookie(headers.value(i+1));
break;
}
}
saveToSP(userNum, userName, nowTerm, baseCookie, specialCookie);
EventBus.getDefault().post(new EventModel<String>(EVENT.EDUCATION_LOGIN_SUCCESS));
}
}
});
}

@Override
public void onFailure(String error) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(new EventModel<String>(EVENT.EDUCATION_LOGIN_FAILURE_NETWORK_ERROR));
}
});
}
});

上面的代码片段中我标注出来的主要部分的代码,赶时间的可以直接看那一部分内容,完整代码可以参考EducationLoginUtil

这里的代码不详细说,因为这里用的网络工具是经过我们封装后的OKHTTP工具,取决与网络工具的不同,所以操作也略有区别,但无论如何,都是通过Post方式发送数据,把该有的参数加上,就可以实现登录。

而登录后,把cookie存下来,抓取课程表难道还会有问题么?

抓取到课程表的话,具体的解析可以参考上一期的内容。

很久没更新这一专题了,这次写完这个可能又要N久才更新这一专题,这期也写得比较渣。感觉还是很多东西想去写,最近又要复习期末考试,特别是一个写代码的还要考信号与系统这种东西。。又马上到时候出去长路漫漫的实习了,所以,无限期跳票。。

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/30e7bc3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu的博客
支付宝打赏