校园网自动登陆
新的学期已经开始, 我的大学生活已经过了一半, 当年不熟悉的事物现在越来越变得习以为常. 但就上网还需要登陆这件事让我感到很是不习惯. 为一个会写程序的人, 怎能不通过程序去简化这一过程呢? 想到这, 我这已经干涸的大脑就好像注入了润滑油一般高速运转起来...
分析登录原理
连接到校园网, 打开需要浏览的网址, 观察抓包信息:
浏览 http://202.108.22.5
时返回的状态码是200, 但也进行了重定向. 那么应该是通过网页代码进行的跳转. 打开 view-source:http://202.108.22.5/
发现了跳转语句:
请求的地址为 http://192.168.1.93/switch.php, 参数如下所示(均为猜测):
名称 | 值 | 描述 |
---|---|---|
wlanuserip | 10.6.4.205 | DHCP分配的IP地址 |
wlanacname | D6-3 | 宿舍楼和楼层 |
ssid | 空的... | |
nasip | 10.0.0.63 | 服务器IP地址 |
mac | 206a8a69a51e | 我的MAC地址 |
t | wireless-v2-plain | 接入方式 |
url | http://202.108.22.5/ | 需要浏览的网址 |
访问 http://192.168.1.93/switch.php?wlanuserip=10.6.4.205&wlanacname=D6-3&ssid=&nasip=10.0.0.63&mac=206a8a69a51e&t=wireless-v2-plain&url=http://202.108.22.5/
, 状态码为302.
跳转到 http://192.168.1.93/portalcloud/page/3/PC/chn/Login.html?wlanuserip=10.6.4.205&wlanacname=D6-3&ssid=&nasip=10.0.0.63&mac=206a8a69a51e&t=wireless-v2-plain&url=http://202.108.22.5/
的同时, 给我存了一个Cookie:
名称 | 值 | 描述 |
---|---|---|
PHPSESSID | 0q560qo79g7osdld27p4lgit01 | Session |
现在所显示的就是登录页面了:
输入账号密码, 点击登录, 请求的地址为 http://192.168.1.93/post.php
, post参数为:
名称 | 值 | 描述 |
---|---|---|
pageId | 3 | |
username | ****************** | 用户名 |
password | ******** | 密码 |
0MKKey | 登录 |
顺便, 经过实验, 若无 pageId
和 0MKKey
参数提交, 也可以登录. 但如果没有 PHPSESSID 或 PHPSESSID 错误, 则无法登录.
提交表单后, 302重定向到 http://192.168.1.93/switch.php?p=status
, 再302重定向到 http://192.168.1.93/portalcloud/page/3/PC/chn/Query.html?p=status
. 显示"登录成功"页面:
获取网络信息
我的思路是构造用于获取 PHPSESSID 的连接并访问. 需要知道的信息为本机的 MAC 和 IP 地址.
在QT中用于获取网络相关信息的类是 QNetworkInterface
, 可以获取到以下信息:
- hardwareAddress() 设备MAC地址.
- humanReadableName() 设备可读名(如 "本地连接"/ "VMware Network Adapter VMnet1"等).
- name() 设备名(如 "{29585BF8-EB77-4F50-B75B-47F675BC80B5}" 等).
flags() 设备状态, 取值为:
- IsUp 已经激活.
- IsRunning 正在运行.
- CanBroadcast 广播模式.
- IsLoopBack 虚拟回环接口.
- IsPointToPoint 点对点.
- CanMulticast 支持多播.
addressEntries() IP地址相关信息, 需要遍历, 每一组数据包含:
- ip() ip地址.
- netmask() 子网掩码.
- broadcast() 广播地址.
在这里我们需要传入 humanReadableName
, 获取MAC地址和IP地址, 于是就写出了这个函数:
AutoLogin::AutoLogin(QString devicename, QObject *parent) : QObject(parent){ //重载构造函数以指定网卡名称
for (const QNetworkInterface& netinterface : QNetworkInterface::allInterfaces()){ //遍历所有网络信息
if(netinterface.humanReadableName() == devicename){ //判断设备名
macaddr = netinterface.hardwareAddress(); //获取MAC地址
ipv4addr = netinterface.addressEntries()[0].ip().toString(); //获取IP地址
}
}
}
鉴于大多人只有一个网卡, 接一条网线, 设一个IP地址, 我们还可以让这个函数更智能一些:
AutoLogin::AutoLogin(QObject *parent) : QObject(parent){ //默认构造函数
for (const QNetworkInterface& netinterface : QNetworkInterface::allInterfaces()){ //遍历所有网络信息
QNetworkInterface::InterfaceFlags flags = netinterface.flags(); //获取网络状态信息
if (flags.testFlag(QNetworkInterface::IsRunning) && !flags.testFlag(QNetworkInterface::IsLoopBack)){ //正在运行且非回环
if (netinterface.addressEntries()[0].ip().toString().left(3) == "10.") //如果IP地址以10开头(学校是A类内网地址)
macaddr = netinterface.hardwareAddress(); //获取MAC地址
ipv4addr = netinterface.addressEntries()[0].ip().toString(); //获取IP地址
}
}
}
如此, 我们就成功获得了本机的IP地址和MAC地址.
写到这里我发现自己的思路有点儿不太对, 比起这样通过程序获取我的网络信息并构造连接地址, 不如模仿我们手动上网登录流程, 打开网页的同时从其源码中获取连接地址. 这样就算是有多个网卡多个 IP 或者不在6号楼也可以正常连接.
获取 PHPSESSID
重整思路, 我应该做的其实是先访问任意网页, 获取其源码, 然后读取到需要跳转的地址并访问.
在任何操作前, 我们应当先确定网络已经联通:
bool AutoLogin::getConnectionState() { //测试网络连通性
QTcpSocket tcpClient; //TCP客户端
tcpClient.abort(); //终止之前的连接并复位
tcpClient.connectToHost("202.108.22.5", 80); //连接百度ip, 80端口
if (!tcpClient.waitForConnected(256)) { //如果256毫秒之内不能连接百度
return (false); //返回false
}
return (true); //如果连接成功, 返回true
}
先初始化网络访问类库并关联信号槽(设置回调函数), 用于获取 "用来获取 PHPSESSID" 的url:
void AutoLogin::initGetUrl() { //初始化获取url
QNetworkAccessManager* manager = new QNetworkAccessManager(); //实例化网络访问类
connect (manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onReplyFinished(QNetworkReply*))); //关联信号和槽
manager->get(QNetworkRequest(QUrl("http://202.108.22.5"))); //访问百度的服务器
}
用于判断是否已经登录:
bool AutoLogin::getLoginState(QString html) { //获取是否登录信息
QRegExp* reg = new QRegExp("<title>(.*)</title>"); //匹配<title>标签
qint8 con = reg->indexIn(html); //获取匹配位置, 若无匹配返回-1
if (con != -1) { //如果有匹配
if (reg->cap(1) == "百度一下,你就知道") { //如果匹配值为百度标题
return (true); //返回true
}
}
return (false); //若无匹配或标题不为"百度一下,你就知道", 返回false
}
正则匹配出需要访问的连接地址:
QString AutoLogin::getSessionUrl(QString html) { //获取"用来获取Session"的Url
QRegExp* reg = new QRegExp("self\.location\.href='(.*)'"); //匹配跳转目的页面
qint8 con = reg->indexIn(html); //获取匹配位置, 若无匹配返回-1
if (con != -1) { //如果有匹配
return (reg->cap(1)); //返回匹配到的url
}
return (""); //如果没匹配则返回空字符串
}
然后是关于 Cookie 的操作了, 写的时候发现 QNetworkCookie::allCookies()
是一个 private 函数, 我不得已继承了一下:
//networkcookie.h
#ifndef NETWORKCOOKIE_H
#define NETWORKCOOKIE_H
#include <QNetworkCookie>
#include <QNetworkCookieJar>
class NetworkCookie : public QNetworkCookieJar{
public:
NetworkCookie();
QList<QNetworkCookie> getCookies();
void setCookies(const QList<QNetworkCookie> &cookieList);
};
#endif // NETWORKCOOKIE_H
//--------------------------------//
//networkcookie.cpp
#include "networkcookie.h"
NetworkCookie::NetworkCookie(){
}
QList<QNetworkCookie> NetworkCookie::getCookies(){
return (allCookies());
}
void NetworkCookie::setCookies(const QList<QNetworkCookie> &cookieList){
setAllCookies(cookieList);
}
OK, 可以外部访问了. 再次实例化网络访问类, 这次是用来获取 PHPSESSID 的.
void AutoLogin::initGetCookies(QString url) { //初始化获取cookie
QNetworkAccessManager* manager = new QNetworkAccessManager(); //实例化网络访问类
cookiejar = new NetworkCookie(); //cookie容器
manager->setCookieJar(cookiejar); //设置网络访问类的cookie容器
connect (manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onCookieFinished(QNetworkReply*))); //关联信号和槽
manager->get(QNetworkRequest(QUrl(url))); //访问url
}
至此 PHPSESSID 就获取到了.
提交 PSOT 请求
最后就是登录了,
void AutoLogin::initLogin() { //初始化登录
QNetworkAccessManager* manager = new QNetworkAccessManager(); //实例化网络访问类
manager->setCookieJar(cookiejar); //设置cookie为之前获取到的cookie
connect (manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onLoginFinished(QNetworkReply*))); //关联信号和槽
QNetworkRequest* request = new QNetworkRequest(); //实例化网络请求
request->setUrl(QUrl(LOGINURL)); //设置请求URL
request->setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded"); //设置请求头
QByteArray parameter; //post参数
parameter.append(REGULAR.arg(Identity).arg(Password)); //构造post参数
manager->post(*request, parameter); //发送post请求
}
三个槽(回调函数)只需要处理逻辑.
void AutoLogin::onReplyFinished(QNetworkReply* reply) { //百度访问完成回调函数
if (reply->error() != QNetworkReply::NoError) { //如果请求失败
std::cout << "Get url error." << std::endl; //提示获取url错误
emit quit(); //触发关闭程序的信号
return; //跳出
}
QString html = reply->readAll(); //读取获取的html代码
if (getLoginState(html)) { //如果正常访问百度
std::cout << "Connected network." << std::endl; //提示已经连接网络
emit quit(); //触发关闭程序的信号
return; //跳出
}
QString url = getSessionUrl(html); //获取需要跳转的地址
if (url == "") { //如果没有获取到需要跳转的地址
std::cout << "Only use in QCHM." << std::endl; //只能用于青岛酒店管理学院
emit quit(); //触发关闭程序的信号
return; //跳出
}
initGetCookies(url); //初始化获取cookie
}
void AutoLogin::onCookieFinished(QNetworkReply* reply) { //获取cookie完成回调函数
if (reply->error() != QNetworkReply::NoError) { //如果请求失败
std::cout << "Get cookies error." << std::endl; //提示获取cookie错误
emit quit(); //触发关闭程序的信号
return; //跳出
}
std::cout << "Your PHPSESSID is: "
<< cookiejar->getCookies()[0].value().data()
<< std::endl; //输出PHPSESSID信息
initLogin(); //初始化登录
}
void AutoLogin::onLoginFinished(QNetworkReply* reply) { //登录完成回调函数
if (reply->error() != QNetworkReply::NoError) { //如果请求失败
std::cout << "Login error." << std::endl; //提示登录错误
} else { //如果成功
if (reply->readAll() == "<!--post ver:1.0.0 -->\n") { //如果有返回值
std::cout << "Login successful." << std::endl; //提示登录成功
} else { //如果没有
std::cout << "Identity or password error." << std::endl; //提示账号密码错误
}
}
emit quit(); //触发关闭程序的信号
return; //跳出
}
主函数
主函数的难点也只有信号和槽的关联了, 由于没有继承自 QObject , 所以 connect 操作需要使用 QObject::connect
调用. AutoLogin 中的 quit() 不要忘记声明为 SIGNALS .
#include <QCoreApplication> //Qt核心
#include <autologin.h> //自己写的类库
int main(int argc, char *argv[]) //主函数
{
QCoreApplication a(argc, argv); //Qt核心类库
AutoLogin *al = new AutoLogin(); //实例化自己写的类库
QObject::connect(al, SIGNAL(quit()), //信号是我类库中的quit()信号
&a, SLOT(quit())); //槽是Qt核心退出
al->setIdentity(argv[1]); //把用户名作为第一个参数传入
al->setPassword(argv[2]); //把密码作为第二个参数传入
al->autoLogin(); //开始登录
return a.exec(); //进入Qt事件循环
}
顺便提及一下 main 函数中的两个形参:
参数名 | 意义 |
---|---|
argc | 程序运行时传入参数的个数 |
argv[0] | 当前程序的绝对路径 |
argv[1]及以后 | 调用此程序时输入的参数 |
设置开机启动
我在众多 Linux 发行版中选择 LinuxMint 作为我主力办公机系统, 也是不无原因的. 不仅因它基于 Ubuntu(Debian) , 有着庞大的社区支持, 非常方便开发者进行软件开发. 而且还有简洁的桌面, 丰富的内置应用. 也是我选择它的原因. 对我这种 Linux 小白而言, 在需要快速实现某项功能却不知道敲什么命令的时候, 给出一个简介强大的界面. 简直不能太赞.
编译完成后把可执行文件复制到适当的目录. 添加一个新的开机启动项, 命令设置为 /home/moe8023/Program/AutoLogin/AutoLogin ****************** ********
, 前部分的 * 表示我的账号(身份证号), 后部分是密码.
结语
如此, 我的计算机只要一开机, 不出意料10秒后就自动登录了校园网. 虽说自个手动打开浏览器登录也不是什么费事儿的事. 但可以给博客凑数, 跟同学装逼啊 但时不时写写程序, 记记笔记打发一下无聊的时间, 也是有益身心健康的.
最后附上项目的 Github 和 Coding 地址, 如果有需要的, 可以自行下载. 如果有什么好的意见和建议, 可以在这篇文章下面留言, 也可以在 Github 或 Coding 提交 issues.
8023一如既往的屌。哈哈哈
感谢你还留着我之前的友链,如果有时间的话,麻烦把我joo.ren的链接换一下吧。新站 tunanshan.com 图南山