前言

众所周知,许多大学的无线网都是没有密码的,取而代之的则是Web认证(portal)的方式,输入用户名和密码轻松实现网络访问的控制,如下图所示(图源网络):

src=http___www.sundray.com.cn_ueditor_net_upload_image_20160718_6360440473834486725969911.png&refer=http___www.sundray.com

那么这种认证方式有一个坏处就是经常掉线,每次我们手动去网页端使用用户名和密码登录很麻烦,于是就诞生了校园网自动登录的这个软件。

原理讲解

网页认证无非就是一个html页面,用于展现给用户输入用户名密码,把用户输入的信息通过POST方式发送给后端服务器进行验证,验证通过就允许上网,反之则无法上网,和大多数网站的登录机制类似。

校园网自动登录程序介绍

1、程序流程

image-20221025185128606

2、所使用的技术

  • GET请求——用于判断电脑是否可以访问互联网
  • POST请求——用于发送数据给认证服务器
  • Swing窗口——用于展示提示信息
  • properties配置文件——用于存储用户信息
  • Socket通信——测试与认证服务器的通断
  • 多线程——保证窗口不会假死
  • URL分割——提取认证网址的信息

一、GET请求测试是否正常联网

这个是Java自带的库,可以直接使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.net.HttpURLConnection;
import java.net.URL;

public class HTTPGet {
public static int testWsdlConnection(String address) throws Exception {
int status = 404;
try {
URL urlObj = new URL(address);
HttpURLConnection oc = (HttpURLConnection) urlObj.openConnection();
oc.setUseCaches(false);
oc.setConnectTimeout(3000);
status = oc.getResponseCode();
if (200 == status) {
// 200是请求地址顺利连通。
return status;
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return status;

}
}

二、发送POST请求

这里需要用到httpclient这个库,我们需要在Maven里添加这个库。

1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.3</version>
</dependency>

基本使用

定义一个sendPOST的函数,有需要可以直接调用。

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
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HTTPPost {
/**
*
* 发送请求
* @param url 发送的url
* @param headerMap 请求头参数集合 key参数名 value为参数值
* @param bodyMap 请求参数集合 key参数名 value为参数值
*/
public static String sendPost(String url, Map<String,String> headerMap, Map<String,String> bodyMap){
//创建post请求对象
HttpPost post = new HttpPost(url);
try {
//创建参数集合
List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();
//添加参数
if (bodyMap!=null){
for (String str:bodyMap.keySet()) {
list.add(new BasicNameValuePair(str, bodyMap.get(str)));
}
}
//把参数放入请求对象,,post发送的参数list,指定格式
post.setEntity(new UrlEncodedFormEntity(list));
if (headerMap!=null){
for (String str:headerMap.keySet()
) {
post.addHeader(str,headerMap.get(str));
}
}
CloseableHttpClient client = HttpClients.createDefault();
//启动执行请求,并获得返回值
CloseableHttpResponse response = client.execute(post);
//得到返回的entity对象
HttpEntity entity = response.getEntity();
//返回内容
return EntityUtils.toString(entity, "UTF-8");
} catch (Exception e1) {
e1.printStackTrace();
return "";
}
}
}

校园网登录

根据sendPOST函数,我们需要先准备url,header,body。这些参数都很容易在浏览器的开发人员工具中找到。

浏览器F12

如果浏览器的开发者工具不能满足你的话,建议使用专业的抓包工具。

抓包

都获取好之后,我们现把它存入一个字符串中,然后我们把它转化为Map<String,String>的形式。这里我们需要用到马爸爸开发的fastjson组件,同样在Maven里添加这个库。

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.66</version>
</dependency>

整体代码如下

1
2
3
4
5
6
7
8
9
String header = "{\"Cache-Control\": \"max-age=0\", \"Upgrade-Insecure-Requests\": \"1\", \"Origin\": \"\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44\", \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\", \"Referer\": \"\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\", \"Connection\": \"close\"}";
Map<String, String> mapheader = JSONObject.parseObject(header, new TypeReference<Map<String, String>>(){});
Map<String, String> params = new HashMap<String, String>(){{
put("R1", "0");
}};
//往params里添加用户名密码
params.put("DDDDD", name);
params.put("upass", b64.decode2(passwd));
String htmlStr = HTTPPost.sendPost(Path.trim(), mapheader, params);

Java爬虫

发送完POST请求之后我们需要判断是否登录成功,由于我们学校的登录返回值并不是一个JSON列表,直接就是一个HTML的文件,最显著的特征就是网页的标题:认证成功页,我们需要通过这个标题来判断是否成功登录,因此这里需要用到Java的爬虫。

1
2
3
Document doc = Jsoup.parse(htmlStr);
String e_title = doc.title();
return Objects.equals(e_title, "认证成功页");

这里有一个大坑啊,字符串的比较用e_title=="认证成功页"死活不好使,后来问了我们老师才知道,Java的字符串比较的方式。

微信截图_20221025194613

三、读取配置文件

这里使用的是Java的properties配置文件,用户名和密码均存储在配置文件里。

微信截图_20221025143007

读取

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
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ReadPropertise {
private static Properties properties = new Properties();
private static BufferedInputStream in;
public static void Readin() throws Exception{
try {
in = new BufferedInputStream(new FileInputStream("c.properties"));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
try {
properties.load(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//读取用户名和密码的值
public static List getUser(){
String name = properties.getProperty("name");
String passwd = properties.getProperty("password");
List map = new ArrayList();
map.add(name);
map.add(passwd);
return map;
}
}

写入

从Swing窗口的输入框获取数据并写入到properties文件。

img

1
2
3
4
5
6
7
8
Properties properties = new Properties();
properties.setProperty("name", name);
properties.setProperty("password", passwd);
try {
properties.store(new FileWriter("c.properties"), null);
} catch (IOException ex) {
throw new RuntimeException(ex);
}

Swing窗口框架

Swing是Java的一个窗口模块,也是自带的,下面将使用注释的方式来给大家讲解Swing。

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

public class LoginSwing {

public void placeComponents() {
//创建一个窗口
JFrame frame = new JFrame("Login");//Login是窗口的标题
//设置窗口大小
frame.setSize(350, 200);
//窗口样式
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//创建一个面板
JPanel panel = new JPanel();

//用户名提示
JLabel userLabel = new JLabel("用户名:");
//设置标签的绝对位置
userLabel.setBounds(10, 20, 60, 25);
//把这个标签添加到面板里
panel.add(userLabel);

//用户名输入框
JTextField userText = new JTextField(20);
userText.setBounds(65, 20, 165, 25);
panel.add(userText);

//密码提示
JLabel passwordLabel = new JLabel("密码:");
passwordLabel.setBounds(10, 50, 60, 25);
panel.add(passwordLabel);

//密码输入框,会隐藏输入的内容
JPasswordField passwordText = new JPasswordField(20);
passwordText.setBounds(65, 50, 165, 25);
panel.add(passwordText);

//创建地址标语
JLabel addLabel = new JLabel("地址:");
addLabel.setBounds(10, 80, 60, 25);
panel.add(addLabel);

//创建地址输入栏
JTextField addText = new JTextField(20);
addText.setBounds(65, 80, 165, 25);
panel.add(addText);

//创建提示语标签
JLabel noticeLabel = new JLabel();
noticeLabel.setForeground(Color.red);
noticeLabel.setBounds(95, 110, 90, 25);
panel.add(noticeLabel);

// 创建登录按钮
JButton loginButton = new JButton("登录");
loginButton.setBounds(10, 110, 80, 25);
panel.add(loginButton);
//按钮点击事件
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String name = userText.getText().trim();//获取输入框里的内容,trim是去除空格
String passwd = String.valueOf(passwordText.getPassword());
String host = addText.getText().trim();
if ("".equals(name)) {
noticeLabel.setText("请输入用户名!");
} else if ("".equals(passwd)) {
noticeLabel.setText("请输入密码!");
} else if ("".equals(host)) {
noticeLabel.setText("请输入地址!");
} else {
//执行语句
//修改标签内容
noticeLabel.setText("登录成功");
//关闭窗口
frame.dispose();
}
}
});
//将面板添加到窗口里
frame.add(panel);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
panel.setLayout(null);
//显示窗口
frame.setVisible(true);
}
}

Socket测试

在登录之前现检测一下学校的认证服务器是否好使,同样只是用了Java的内置库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

public class SocketTest {
//判断ip、端口是否可连接
public static boolean isHostConnectable(String host, int port) {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(host, port));
} catch (IOException e) {
//e.printStackTrace();
return false;
} finally {
try {
socket.close();
} catch (IOException e) {
//e.printStackTrace();
}
}
return true;
}
}

Java的多线程使用

基本使用

1
2
3
4
5
new Thread(new Runnable() {
@Override
public void run() {
}
}.start()

案例:睡眠排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
int[] nums = new int[]{235, 233, 110, 789, 5, 0, 1};
for (int item : nums) {
new Thread(() -> {
try {
Thread.sleep(item);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(item);
}).start();
}
}
}

生产环境千万不要使用这个啊,这串代码没个几十年脑血栓还真想不出来,睡眠排序暴露的问题也很明显:

  1. 没有办法睡眠负数。
  2. 相近的数值之间会有误差。
  3. 你要是给了个很大的数让它睡去吧,脑血栓治好了估计都睡不完。

下载

下载地址1:

链接: https://pan.baidu.com/s/1TnHwBci5RD_67hNRG8XSUQ

提取码: afi7

源码下载