阿里云 SLB 导致 Laravel URL 变成 HTTP 协议

阿里云 SLB 导致 Laravel URL 变成 HTTP 协议

最近改造公司的项目时,发现 Laravel 生成的 URL 都是 HTTP 协议的,但实际请求却是 HTTPS,经过了阿里云 SLB 转发到后端 HTTP 服务端口。

前因

公司的 C 端项目是采用 Laravel 框架开发的,页面是前后端混编的。在开发中总能或多或少的看到代码逻辑里使用$load_by_https这个全局变量来判断加载资源是不是 HTTPS 的,以及在生成 URL 的时候页需要判断。着实又些麻烦且不够优雅,虽然一直感觉别扭,但是因为涉及到的代码量,也就打消了要去改造的念头。

引子

最近因为这个项目需要做 SEO 相关的优化工作,在优化的过程中发现太多因为 Laravel 生成的 URL 非 HTTPS 而导致的大量问题。于是乎一不做二不休,决定追查为什么当初的开发者要采用全局$load_by_https这个变量来手动设置是否启用 HTTPS。

经过排查和推论发现,是因为我们的项目采用了阿里云的负载均衡器来分发请求到后端的多台服务器上。但是因为我们是将 HTTPS 证书绑定到 SLB 的,所以后端服务都监听的是 HTTP 服务。具体请求流程大致如下:

SLB 代理流程

解决方案

配置 SLB

从上面的流程图中我们发现当 HTTPS 请求到达 SLB 的时候会被以 HTTP 的形式转发到后端服务,那么我们需要在 SLB 的设置中开启 X-Forwarded-Proto 这个配置项。

在 SLB 的管理页面中,修改监听配置,然后勾选 通过X-Forwarded-Proto头字段获取SLB的监听协议。 选项。这样后端的服务就能够在请求头重获取客户端请求时的 Scheme。

SLB 开启 X-Forwarded-Proto

Laravel 中的设置

因为 Laravel 的安全设置导致设置了 X-Forwarded-* 以后依然无法正确获取到有效信息,我们还需要在 App\Http\Middleware\TrustProxies 中间件中配置将 SLB 设置为信任代理。

因为阿里云的 SLB 的 IP 是不固定的,所以没办法设置具体的 IP 地址,只能用 * 或 ** 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace NeoX\Http\Middleware;

use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies = '*';

/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

这里主要需要设置的是 $proxies$headers

这样当 Laravel 在生成 URL 的时候会调用 Request 的 isSecure() 方法,来判断是否启用 HTTPS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Checks whether the request is secure or not.
*
* This method can read the client protocol from the "X-Forwarded-Proto" header
* when trusted proxies were set via "setTrustedProxies()".
*
* The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
*
* @return bool
*/
public function isSecure()
{
if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) {
return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
}

$https = $this->server->get('HTTPS');

return !empty($https) && 'off' !== strtolower($https);
}

那么问题来了,替换页面模板中大量的 $load_by_https 变量,又是一个耗时工作。

评论