通过nginx实现对web应用的js注入和在线人数限制

通过docker运行的cogptchatgpt next web不太好改源码,无法从后端或者前端限制人数。只能曲线救国,通过nginx进行 js 注入umami跟踪代码,然后加一个 php 中转界面进行鉴定是否跳转。

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
# nginxconf配置文件
server {
listen 80; # 或者443如果你使用SSL
server_name gpt.lzyyyyyy.fun;
root /var/gpt-boss;

# 所有其他请求都重定向到boss.php,包括 /api 和 /guest
location / {
if ($http_cookie !~* "auth=") {
rewrite ^/(.*)$ /boss.php last;
}
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

}


location /api/ {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}


location /admin {
# 当访问 /admin 时,直接跳转到 localhost:3000
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /guest {
# 检查是否存在通过验证的 cookie
if ($http_cookie !~* "auth") {
# return 302 /boss.php;
rewrite ^/(.*)$ /boss.php last;
}
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# 开启响应内容替换
sub_filter_once on;
# sub_filter_types text/html;
sub_filter '</head>' '<script async src="http://umami.lzyyyyyy.fun/script.js" data-website-id="cd2c9621-98e6-440b-9e10-fb5269721d31"></script></head>';

# 以下配置确保内容长度正确
proxy_set_header Accept-Encoding "";
sub_filter_last_modified on;
}

location /_next/ {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}


# 为 PHP 文件添加新的 location 块
location ~ .*\.php(/.*)*$ {
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
}
}

然后是boss.php

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php

// 读取 config.json 文件内容
$jsonString = file_get_contents('config.json');

// 解析 JSON 数据为 PHP 数组
$config = json_decode($jsonString, true);
$username = $config['username'];
$password = $config['password'];
$loginUrl = $config['loginUrl'];
$activeUsersUrl = $config['activeUsersUrl'];

// Function to perform a cURL request
function curlRequest($url, $postFields = null, $headers = []) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if (!is_null($postFields)) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postFields));
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$response = curl_exec($ch);
// echo $response;
if (curl_errno($ch)) {
curl_close($ch);
return null;
}
curl_close($ch);
return json_decode($response, true);
}

// 生成一个随机的字符串作为 cookie 值
function generateRandomString($length = 50) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}


// Get authentication token
$authResponse = curlRequest($loginUrl, ['username' => $username, 'password' => $password], ['Content-Type: application/json']);
$token = $authResponse['token'] ?? '';

if ($token) {
// Fetch active users
// Fetch active users
$activeUsersData = curlRequest($activeUsersUrl, null, ['Authorization: Bearer ' . $token]);
// 假设我们只关心第一个元素的 'x' 值
$activeUsers = isset($activeUsersData[0]['x']) ? $activeUsersData[0]['x'] : 'Unavailable';

//支持最多{{people+1}}人同时使用
$people=2;

// 根据在线用户数显示不同的消息
$message = $activeUsers > $people ? "抱歉,当前在线用户数已达到限制。": "在线用户数少于指定人数,欢迎访问。";

echo "<!DOCTYPE html>
<html>
<head>
<title>LZY's GPT 在线用户检查</title>
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #0066cc;
}
p {
line-height: 1.6;
}
.alert {
padding: 20px;
background-color: #f44336; /* Red */
color: white;
margin-bottom: 15px;
}
.info {
padding: 20px;
background-color: #2196F3; /* Blue */
color: white;
margin-bottom: 15px;
}
a {
color: #2196F3;
text-decoration: none;
font-weight: bold;
}
</style>";
if ($activeUsers < $people+1) {
$randomString = generateRandomString(); // 默认长度为 50
setcookie("auth", $randomString, time() + 1800); // 设置 cookie,有效期为 30 分钟
echo "<meta http-equiv='refresh' content='3; url=http://gpt.lzyyyyyy.fun/guest'>";
}
echo "</head>
<body>
<div class='container'>
<h1>欢迎使用LZY's GPT。</h1>";
if ($activeUsers < $people+1) {
echo "<div class='info'>{$message}</div>
<p>三秒后将自动跳转...</p>";
} else {
echo "<div class='alert'>{$message}</div>
<p>这会使用人数有点多噢,不如去 <a href='https://lzyyyyyy.fun'>fugu's 博客</a>逛逛叭~</p>";
}
echo " </div>
</body>
</html>";
} else {
// Handle error in getting auth token
echo "<div class='container'>
<div class='alert'>无法认证,请检查配置。</div>
</div>";
}

实现了中转鉴定。然后通过设置cookie避免短时间内多次鉴定。


后来发现登陆界面/#/auth无法正常跳转,关键点:

  1. 对 cookie 进行鉴定,避免再次到达 boss 界面。
  2. 这种包含#的路径,是前端内部路由,对于服务器而言依旧是/路径,需要正确转发到3000端口,否则页面请求资源会到root /var/gpt-boss这个路径下请求了,而不会进入容器内部。

通过nginx实现对web应用的js注入和在线人数限制
http://sinlatansen.github.io/2024/03/06/通过nginx实现对web应用的js注入和在线人数限制/
作者
fugu
发布于
2024年3月6日
许可协议