Frp 自定义认证

最近要重构 Light App Engine的Frp这一块,记录一下Frp自定义认证过程。

相关链接

Frp Server Plugin(重要)

LAE TunnelController(实现)

Frps 服务端配置

[common]
bind_port = 7000
token = your_token

[common]
dashboard_port =7500
dashboard_user = admin
dashboard_pwd = admin

#[plugin.user-manager]
#addr = http://tunnel.test/
#path = handler
#ops = Login

[plugin.port-manager]
addr = http://tunnel.test/
path = handler
ops = NewProxy, Ping

别看有两个plugin,实际上我们只需要用到下面的”port-manager”,”user-manager”如果你有需要,可以加上。

“ops”代表这个插件要发送的”握手(?”,当发生NewProxy时,将POST数据到http://tunnel.test/handler(addr+path)

获取数据

我们先来看一个基本的请求。

当 NewProxy 发生时,后端将收到一个类似下方的请求:

{
    "content": {
        "user": {
            "user": <string>,
            "metas": map<string>string
            "run_id": <string>
        },
        "proxy_name": <string>,
        "proxy_type": <string>,
        "use_encryption": <bool>,
        "use_compression": <bool>,
        "group": <string>,
        "group_key": <string>,

        // tcp and udp only
        "remote_port": <int>,

        // http and https only
        "custom_domains": []<string>,
        "subdomain": <string>,
        "locations": <string>,
        "http_user": <string>,
        "http_pwd": <string>,
        "host_header_rewrite": <string>,
        "headers": map<string>string,

        // stcp only
        "sk": <string>,

        // tcpmux only
        "multiplexer": <string>

        "metas": map<string>string
    }
}

不用管那么多,我们只需要下面的这些请求,其余请求我们可以暂时忽略。

  • op // 用于判断是什么类型的请求
  • content[‘user’] // 代理的用户
  • content[‘proxy_name’] // 代理的名称
  • content[‘proxy_type’] // 代理协议(tcp/udp之类的)

逻辑与处理

在本质上,我们只需要content[‘proxy_name’]就可以分辨出隧道在哪个服务器,属于哪个用户之类的,因为Frp的隧道名称支持部分符号,比如“server1|user1|tunnel_token”。这样你就可以通过分割字符串,来判断隧道是属于哪个服务器,哪个用户,并且查找隧道的ID来鉴权。

但是

他也是有缺陷的。

如果你要精确到每个用户的流量,包括隧道的心跳(Ping),你需要user字段。

示例客户端配置文件

# 这是你的配置文件,请将它填入frpc.ini
[common]
server_addr = server.test
server_port = 7000
user = 1ec2698b-1080-6a08-af80-5525f8d9c476
token = test_config

# Test项目 的 123 于服务器 server.test
[3|5|1ec2698b-1080-6a08-af80-5525f8d9c476]
type = udp
local_ip = 127.0.0.1
local_port = 80
remote_port = 12415

可能细心的你已经发现了。如果还没有,请看下方。

  • “3” 代表 服务器ID
  • “5” 代表 平台用户ID
  • “1ec2698b-1080-6a08-af80-5525f8d9c476” 代表 代理ID

在实战时,你不必遵循示例配置文件的格式,而是可以自由发挥(不要坑了自己)。

实战代码

完整代码看这里:LAE TunnelController

我将在这里使用Laravel框架,并且假设服务器和模型都存在且正常工作

获取 op

我们要先通过op来判断是什么类型的请求,这里我们只需要一个NewProxy

if ($request->op == 'NewProxy') {
     // code here
}

我建议加一个判断,防止发生除NewProxy之外的情况,比如恶意请求。

查询代理 ID 是否存在

try {
                // 分割字符串 // proxy_type // $request->user['user]
                $client = explode('|', $request->content['proxy_name']);
                // 0: 服务器ID 1: 代理ID
                // $sid = explode('.', $client[0])[1];
                $tid = $client[1];
                $token = $client[2];
  } catch (Exception $e) {
                unset($e);
                return response()->json([
                    "reject" => true,
                    "reject_reason" => "不要啦,这不是正确的信息!",
                    "unchange" => true,
                ]);
}

首先,我们先尝试分割proxy_name,因为当用户随意输入时,他将无法解析并抛出异常。

然后依次赋值给变量,方便我们接下来处理。

你可以清晰的看见,当解析失败的时候,将返回一个 JSON 响应,其中 REJECT 为 true,这代表拒绝用户连接隧道,随后frps将会拒绝frpc的连接。

REJECT REASON 是拒绝原因。你可以在这里填写拒绝的理由。

UNCHANGE 一般情况下都要设置为true,这代表不修改用户传入的配置。如果为false,你需要给出修改后的配置。

检查代理信息是否正确

接下来,我们将从数据库中检查信息,并核对frps给出的信息,然后返回结果,最后再决定是否拒绝还是同意客户端连接。


            // 检查是否存在
            $tunnel_where = $tunnel->where('server_id', $request->route('id'))->where('id', $tid)->with(['project']);
            if ($tunnel_where->where('client_token', $token)->exists()) {
                // 检查端口之类的是否相等
                $tunnel_info = $tunnel_where->firstOrFail();

                if ($request->content['proxy_type'] == 'tcp' || $request->content['proxy_type'] == 'udp') {
                    if ($request->content['proxy_type'] == $tunnel_info->proxy_type || $request->content['remote_port'] != $tunnel_info->remote_port || $tunnel_info->remote_port < 1024) {
                        return response()->json(array(
                            "reject" => true,
                            "reject_reason" => '配置文件错了,请检查配置文件或者到我们的平台重新下载一份。',
                            "unchange" => true,
                        ));
                    }
                } elseif ($request->content['proxy_type'] == 'http' || $request->content['proxy_type'] == 'https') {
                    if ($request->content['proxy_type'] == $tunnel_info->proxy_type || $request->content['custom_domains'][0] != $tunnel_info->custom_domain) {
                        return response()->json(array(
                            "reject" => true,
                            "reject_reason" => '可能域名填写错误。',
                            "unchange" => true,
                        ));
                    }
                }


                return response()->json(array(
                    "reject" => false,
                    "unchange" => true,
                ));
            } else {
                return response()->json(array(
                    "reject" => true,
                    "reject_reason" => "代理不存在",
                    "unchange" => true,
                ));
            }

想必到这边,你可能清楚了整个流程。

总结

验证其实并不是很难,所以我就先写到这里了(要下课了)

这篇文章还没有完善,如有任何问题,你可以在下方评论,我会尽我所能的回复。

评论

  1. 啊这
    2年前
    2021-12-23 22:49:17

    :mrgreen:

  2. 啊这
    2年前
    2021-12-23 22:49:02

    😉

  3. Au
    2年前
    2021-12-23 20:46:26

    awsl~

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇