FRP从数据库获取Token和User
环境
- frp_0.27.0
- Go读取Mysql数据
目标
- 客户端连接时查询Mysql数据库中是否存在对应用户ID
- 若存在用户ID则从数据库中查询对应的token进行客户端验证
- 若不存在则返回连接失败
调试
获取FRP源码
1 |
git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp |
编译源码
1 2 |
cd $GOPATH/src/github.com/fatedier/frp make |
测试运行
1 2 |
cd $GOPATH/src/github.com/fatedier/frp/bin ./frps -c ./frps.ini |
分析
FRP的主要入口是github.com/fatedier/frp/server/service.go
主要验证代码在第349-353行
1 2 3 4 5 |
// Check auth. if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { err = fmt.Errorf("authorization failed") return } |
其中调用了util.GetAuthKey函数,这个函数在github.com/fatedier/frp/utils/util/util.go中的第43-49行
1 2 3 4 5 6 7 |
func GetAuthKey(token string, timestamp int64) (key string) { token = token + fmt.Sprintf("%d", timestamp) md5Ctx := md5.New() md5Ctx.Write([]byte(token)) data := md5Ctx.Sum(nil) return hex.EncodeToString(data) } |
综合这两段代码可以发现frp中对客户端的验证方法主要是通过获取服务器frps.ini配置文件中的token字段和timestamp组合求MD5 hash之后再进行16进制转换后再和客户端传递的PrivilegeKey值进行对比来验证。当然,客户端提交的PrivilegeKey也是这样计算的。
那么如果要接入数据库,就需要改造这个验证
通过新建验证函数,将token的获取方式从配置文件改为数据库,后续的hash和hex都继续保留即可。
但这仅仅是客户端的第一次验证环节,frp在客户端成功注册后,会马上进行控制层的数据交换,这时就会遇到第二个使用token的环节,既控制层的数据是用token加密的。
这个环节在github.com/fatedier/frp/server/control.go的第226-311行,关键行如下
1 2 |
encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token)) encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token)) |
可以看到,其中的加密都用到了token,也需要用新的获取token的方法更换
在这步完成之后,客户端的注册和控制数据的交互已经没有问题了,但是紧接着会出现第三个使用token的环节,既http代理有一个数据加密的选项,如果开启加密,那么将使用token对http数据流进行加密。
这个环节在github.com/fatedier/frp/server/proxy/http.go的第116行
1 |
rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token)) |
其中的token也需要使用数据库方法替换,但是这里有一个坑,就是http.go太底层了,导致执行的时候并没有客户端的认证信息被传递到这里,所以没有办法获取用户ID。
解决方法就是在github.com/fatedier/frp/server/control.go的第427行在创建Proxy的时候手动传递一个用户ID给底层
1 |
pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.loginMsg.User) |
然后在github.com/fatedier/frp/server/proxy/proxy.go的第145行接住这个用户ID
1 2 |
func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int, getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, loginUser string) (pxy Proxy, err error) { |
然后在后面的basePxy中增加这个用户ID字段,使得在http.go里可以调用
1 2 3 4 5 6 7 8 9 10 |
basePxy := BaseProxy{ name: pxyConf.GetBaseInfo().ProxyName, rc: rc, statsCollector: statsCollector, listeners: make([]frpNet.Listener, 0), poolCount: poolCount, getWorkConnFn: getWorkConnFn, Logger: log.NewPrefixLogger(runId), loginUser: loginUser, } |
当然除了http外,tcp也有一个use_encryption的选项,也是用的token进行的加密,在github.com/fatedier/frp/server/proxy/proxy.go的第212行,也需要替换
1 |
local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) |
可以在proxy.go增加一个函数用于调用basePxy里传递过来的用户ID
1 2 3 |
func (pxy *BaseProxy) GetLoginUser() string { return pxy.loginUser } |
Know More
https://lzxz1234.cn/archives/201
There are no comments yet