yii2项目使用frp https2http插件问题

Posted sjg20010414

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了yii2项目使用frp https2http插件问题相关的知识,希望对你有一定的参考价值。

yii2内网项目,使用frp进行内网穿透,使用 https2http插件把内网服务器http流量转成https,会存在一个问题:当使用 $this->redirect(...) 或 $this->goHome() (其实用的也是前者)等重定向时,网址会变成 127.0.0.1,并且协议也从https变成了 http,结果自然是显示找不到

frp client配置类似官网例子:

[common]
server_addr = 47.xx.xxx.xx
server_port = 7900

[web_https]
type = https
custom_domains = lab.xxxxx.com

plugin = https2http
plugin_local_addr = 127.0.0.1:80

plugin_crt_path = ./xxxxx.com.pem
plugin_key_path = ./xxxxx.com.key
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp

比较通过域名 https://lab.xxxxx.com:8900 访问和通过IP地址访问时 $_SERVER 超级全局变量的结果差异,主要有以下差异:

$_SERVER = Array
(
    [HTTP_X_FROM_WHERE] => frp
    [HTTP_X_FORWARDED_FOR] => 47.xx.xxx.xx
    [HTTP_SEC_FETCH_USER] => ?1
    [HTTP_SEC_FETCH_SITE] => same-origin
    [HTTP_SEC_FETCH_MODE] => navigate
    [HTTP_SEC_FETCH_DEST] => document
    [HTTP_SEC_CH_UA_PLATFORM] => "Linux"
    [HTTP_SEC_CH_UA_MOBILE] => ?0
    [HTTP_SEC_CH_UA] => "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
    [HTTP_REFERER] => https://lab.xxxxx.com:8900/
    [HTTP_HOST] => 127.0.0.1
    [SERVER_ADDR] => 127.0.0.1
    [REMOTE_ADDR] => 127.0.0.1
)
$_SERVER = Array
(
    [HTTP_CONNECTION] => keep-alive
    [HTTP_HOST] => 10.5.10.200
    [SERVER_ADDR] => 10.5.10.200
    [REMOTE_ADDR] => 10.5.21.241
)

前一个是frp代理后的结果,后一个是内网直接访问的结果,我们用下图来表示这个过程

用frp内网穿透,是某种形式的反向代理。frp server只起到转发作用,它的配置非常简单,也就指定一下“主控”端口和https监听端口。使用https2http插件时,证书是配置在frp client的(即需要把通常放在公网frp server服务器的证书放一份在frp client,这里也说明frp只适合绝对信得过的client使用该插件)。frp client 上的证书,既用于frp server转发过来的流量的解密(变成http流量转发给nginx),也用于nginx响应的流量的加密(变成https流量转发给frp server,并最终响应给用户)。

我们可以发现,对于nginx来说,它的“客户”是frp client,从IP地址来说,这里的frp client “客户”地址(REMOTE_ADDR)和这里的 server nginx地址(SERVER_ADDR)都是127.0.0.1,相当于“客户”frp client,主机Host也是 127.0.0.1。不过头部中的主机信息 HTTP_HOST 是可以被重写的,对于 http 代理是配置 host_header_rewrite,而对于我们的 https2http插件,需要在 frp client配置 plugin_host_header_rewrite来重写主机Host信息。(frp client中配置 plugin_header_X-xxxx-xxx形式的头部信息,对应了 HTTP_X_xxxx_xxx 形式的头部,只是在我们的情形中,配置 HTTP_HOST、HTTP_X_FORWARDED_HOST、HTTP_PROTO、HTTP_X_FORWARDED_PORT等都不能解决问题)

了解了frp代理的基本原理,我们需要来了解yii2中redirect是怎么进行的。

yii2 redirect 所需的URL信息可以分为两个部分:第一部分是 协议、主机、端口,这部分根据yii\\web\\Request::getHostInfo() 函数确定;第二部分是相对于文档根的路径,由 Url::to($url)计算得到。我们关心前面的部分,代码如下

    public function getHostInfo()
    
        if ($this->_hostInfo === null) 
            $secure = $this->getIsSecureConnection();
            $http = $secure ? 'https' : 'http';

            if ($this->getSecureForwardedHeaderTrustedPart('host') !== null) 
                $this->_hostInfo = $http . '://' . $this->getSecureForwardedHeaderTrustedPart('host');
             elseif ($this->headers->has('X-Forwarded-Host')) 
                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Forwarded-Host'))[0]);
             elseif ($this->headers->has('X-Original-Host')) 
                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Original-Host'))[0]);
             elseif ($this->headers->has('Host')) 
                $this->_hostInfo = $http . '://' . $this->headers->get('Host');
             elseif (isset($_SERVER['SERVER_NAME'])) 
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
                $port = $secure ? $this->getSecurePort() : $this->getPort();
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) 
                    $this->_hostInfo .= ':' . $port;
                
            
        

        return $this->_hostInfo;
    

代码的基本执行路径如下:

解决方案一:

frp client配置中,指定

 plugin_host_header_rewrite = lab.xxxxx.com:8900

这样,frp client 转发到 nginx 时,头部信息 HTTP_HOST 会被改写成设定的值,从而 getHostInfo 执行时会走路径4 (读取头部 Host信息的紫色路径),但仅仅这样是不行的,因为我们需要协议是 https,而 getIsSecureConnection 根据超级全局变量 $_SERVER['HTTPS'] 或者头部信息Forwarded中的proto部分来决定是https还是http(转发时不存在https到http切换会比较简单,因为不需要考虑这一步)。我们一种变通方法是在 yii2 入口文件中,直接指定 $_SERVER['HTTPS']=1(当然,为了兼容内网ip地址访问,可以判断 $_SERVER['SERVER_ADDR']和$_SERVER['REMOTE_ADDR']是否同时为127.0.0.1,因为我们的自我转发中,这是特点)

if ($_SERVER['SERVER_ADDR'] === '127.0.0.1' && $_SERVER['REMOTE_ADDR'] === '127.0.0.1')   // 是内网穿透自身代理而来的请求
    $_SERVER['HTTPS'] = '1';  //

$application->run();

解决方案二:

frp client配置中,不指定 plugin_host_header_rewrite,但额外指定

plugin_header_Forwarded = for=47.xx.xxx.xx;host=lab.xxxxx.com:8900;proto=https

然后在 yii2 的配置中,对组件 request 添加配置(下面 baseUrl后面部分)

            'baseUrl' => '/admin',  //-----------后端基础目录
            'trustedHosts' => [  // https://www.yiiframework.com/doc/api/2.0/yii-web-request#$secureHeaders-detail
                '127.0.0.1/24',  // 从代理转发的,这里设置白名单,这样 $secureHeaders 中的 X-Forwarded-Host 等头部才被允许
            ],
            'secureHeaders' => [  // frp配置: plugin_header_Forwarded = 
                'Forwarded',    // 我们添加的 集合了各项 forwarded, 参考 yii\\web\\Request::getSecureForwardedHeaderParts()
                'X-Forwarded-For',
                'X-Forwarded-Host',
                'X-Forwarded-Proto',
                'X-Forwarded-Port',
                'Front-End-Https',
                'X-Rewrite-Url',
                'X-Original-Host',
            ],

这样配置后,frp client 转发时会带上头部 Forwarded,并且这个Forwarded头部将被 yii2 允许(转发主机白名单,可接受头部等)。从而,代码执行流程会按前面图中红色路径走。

路径2、3、5也可以考虑,不过和第一个方案一样,需要指定 $_SERVER['HTTPS'] = 1来指定协议,而且端口不一定好处理。

以上是关于yii2项目使用frp https2http插件问题的主要内容,如果未能解决你的问题,请参考以下文章

yii2手动添加插件PHPExcel

Yii2 - 使用 Ajax 加载为 Select2 插件设置值

yii2.0的学习之旅(-)

Yii2上传图片插件使用

yii2组件之多图上传插件FileInput的详细使用

yii2 kartik select2 插件在模态下不起作用