主页邬

关于如何搭建一个 WordPress 站点, 我们就不讲了, 网上教程一大把. 本文主要讲讲如何在使用 Nginx 的情况下, 配置 WordPress 开启 HTTPS 服务.


我们的整个站点的系统架构是这样的:

系统架构


也就是说, 前端 HTTPS/HTTP 要先通过 Nginx, 再由 Nginx 转发到 Apache. 不管是 HTTP 请求还是 HTTPS 请求都要先经过 Nginx, 到达 Apache 后, 所有的请求都是 HTTP 协议的. 看起来没什么困难的, 只要在 Nginx 配置 HTTPS 证书就可以了.


Nginx 配置如下:

// Apache 服务端口
upstream zend {
    server 127.0.0.1:8081;
}

server {
    server_name www.maxweb.club;
    server_name maxweb.club;
    listen 443 ssl;
    listen 80;
    ssl_certificate ./www.maxweb.club/Nginx/www.maxweb.club_bundle.crt;
    ssl_certificate_key ./www.maxweb.club/Nginx/www.maxweb.club.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;

    location / {
        if ($scheme = http) {
            rewrite "^(.*)" https://$host$1 redirect;
            break;
        }
        proxy_pass                  http://zend;
        proxy_redirect            off;
        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-Scheme $scheme;
    }
}


这样所有的链接都会先经过 Nginx, 再代理进入 Apache. 但是由于我们的网站是从 HTTP 迁入 HTTPS 的, 之前有一些静态资源的链接已经写死了协议, 使用的是 HTTP. 而且由于某些特殊的原因, WordPress 的某些静态资源的协议也是 HTTP 的. 这就导致了直接使用 HTTPS 协议访问 https://www.maxweb.club 时, 所有的 HTTP 资源会被浏览器 block 掉, 根本就不会到达 Nginx, 也就不会重定向到 HTTPS.


所以即使在网站的根目录中的 .htaccess 配置了重定向也不行:

RewriteEngine                 On
RewriteCond %{HTTPS}    off
RewriteRule ^(.*)$           https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]


上面的设置会导致无限重定向, 从 Nginx 来的 HTTP 到了 Apache 转换成 HTTPS, 又重新回到 Nginx, 又转换成 HTTP 回到 Apache.


最后使用了一个插件 SSL Insecure Content Fixer 解决了这个问题, 但是这个插件是怎么解决这个问题的呢?

// 插件构造函数中的代码
...
private function __construct() {
    $this->loadOptions();
    $this->proxyFix();

    add_action('init', array($this, 'init'));

    if ($this->options['fix_level'] !== 'off' && is_ssl()) {
        add_action('init', array($this, 'runFilters'), 4);

        // filter script and stylesheet links
        add_filter('script_loader_src', 'ssl_insecure_content_fix_url');
        add_filter('style_loader_src', 'ssl_insecure_content_fix_url');

        // filter uploads dir so that plugins using it to determine upload URL also work
        add_filter('upload_dir', array(__CLASS__, 'uploadDir'));

        // filter image links on front end e.g. in calls to wp_get_attachment_image(), wp_get_attachment_image_src(), etc.
        if (!is_admin() || $this->isAjax()) {
            add_filter('wp_get_attachment_url', 'ssl_insecure_content_fix_url', 100);
        }

        switch ($this->options['fix_level']) {
        ...
    }
    ...
}

// 主要函数
function ssl_insecure_content_fix_url($url) {
    // only fix if source URL starts with http://
    if (stripos($url, 'http://') === 0) {
        $url = 'https' . substr($url, 4);
    }

    return $url;
}


上面代码的意思是在插件初始化时, 往 script_loader_srcstyle_loader_src 等钩子加上处理函数 ssl_insecure_content_fix_url, 而这个处理函数的作用就是将静态资源中链接协议为 HTTP 的替换为 HTTPS.


那这个插件怎么知道用户是通过 HTTPS 访问的呢, 因为只有用户通过 HTTPS 访问, 插件才需要将 HTTP 转换为 HTTPS, 如果用户是通过 HTTP 访问的, 那就没必要转换了. 而且根据我们的系统架构图, 所有到了 Apache 的请求都是通过 HTTP 协议的.


我们继续通过源码来看一下, 以下是 wp-config.php 中的代码:

$server_opts = array(..., "HTTP_X_FORWARDED_PROTO"=>"https", ...);

foreach( $server_opts as $option => $value ) {
    if ( (isset($_ENV["HTTPS"]) && ( "on" == $_ENV["HTTPS"] )) || (isset( $_SERVER[ $option ] ) && ( strpos( $_SERVER[ $option ], $value ) !== false )) ) {
        $_SERVER[ "HTTPS" ] = "on";
        break;
    }
}


上面的代码会通过判断 HTTP_X_FORWARDED_PROTO 是不是等于 https 来设置 $_SERVER[ “HTTPS” ] 是否等于 on的.


关于 X_FORWARDED_PROTO 的具体解释可以参考 X_FORWARDED_PROTO, 简单来说, X_FORWARDED_PROTO是一个事实上标准的 HTTP 协议头字段, 这个字段标识了客户端是通过哪种协议连接到代理或者负载均衡的. 所以插件只需要在 Apache 端读取这个字段就可以决定是不是要转换协议.


以上.