使用.htaccess和mod_rewrite强制启用SSL/https

255
可以通过在PHP中使用.htaccess和mod_rewrite强制使用SSL/https来实现页面特定的SSL/https。

我找到了一个适用于代理和非代理服务器的mod_rewrite解决方案。请参见https://dev59.com/H3M_5IYBdhLWcg3wjj4p#34065445/中的答案。 - raphinesse
9个回答

467

对于Apache,您可以使用mod_sslSSLRequireSSL指令来强制启用SSL:

此指令禁止访问,除非当前连接启用了HTTP over SSL(即HTTPS)。在启用SSL的虚拟主机或目录中,这非常有用,可防止配置错误暴露应受保护的内容。当存在此指令时,所有未使用SSL的请求都将被拒绝。

但是,这不会重定向到https。要进行重定向,请尝试mod_rewrite中的以下内容放入您的.htaccess文件中:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

或者使用以下任意一种方法:

如果您的提供商已禁用了 .htaccess,您也可以通过 PHP 内部解决此问题(尽管这不太可能发生,因为您已经请求了它)。

if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
    if(!headers_sent()) {
        header("Status: 301 Moved Permanently");
        header(sprintf(
            'Location: https://%s%s',
            $_SERVER['HTTP_HOST'],
            $_SERVER['REQUEST_URI']
        ));
        exit();
    }
}

1
这在我们的情况下非常好,因为我们目前有http和https流量的混合。对于我们的管理区域,我们只需插入.htaccess脚本,同时保持其余部分为http。 - Michael J. Calkins
1
当我使用mod_rewrite方法时,我被发送到https,但出现“页面无法正确重定向”的错误。 - Czechnology
11
跟进提示:如果您遇到类似问题,请检查您的服务器和HTTP变量。如果您的服务器使用代理,您可能需要使用“%{HTTP:X-Forwarded-Proto}”或“%{HTTP:X-Real-Port}”变量来检查SSL是否已打开。 - Czechnology
8
如果你在代理服务器(例如CloudFlare、Openshift)后运行的服务器上遇到了重定向循环问题,请参考此回答以获得适用于该情况的解决方法。链接地址为:https://dev59.com/H3M_5IYBdhLWcg3wjj4p#34065445 - raphinesse
4
从我的(有限)重写规则知识来看,语法是“RewriteRule <输入模式> <输出URL>”。因此,去掉空格会使我无法使用重写规则。而单个的“^”只是表示“匹配所有输入URL”。 - Sphinxxx
显示剩余6条评论

59

我找到了一个mod_rewrite的解决方案,适用于代理和非代理服务器。

如果您正在使用CloudFlare、AWS弹性负载均衡、Heroku、OpenShift或其他云/PaaS解决方案,并且在普通HTTPS重定向时遇到重定向循环问题,请尝试使用以下代码片段。

RewriteEngine On

# If we receive a forwarded http request from a proxy...
RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR]

# ...or just a plain old http request directly from the client
RewriteCond %{HTTP:X-Forwarded-Proto} =""
RewriteCond %{HTTPS} !=on

# Redirect to https version
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

这太棒了,谢谢!但是也许需要添加后置条件吗? RewriteCond %{REQUEST_METHOD} !^POST$ - Urgotto
@Aleksej 是的,这会破坏未加密的POST请求。但我认为这是一件好事。这样,人们会注意到他们正在做错事。我想最好的方法是将他们重定向到一个页面,告知他们如何正确使用您的服务。 - raphinesse
@raphinesse,你知道这个问题的答案吗:https://stackoverflow.com/questions/51951082/force-https-and-non-www-with-htaccess-for-cloudflare-users - RRN

38

PHP解决方案

直接借鉴Gordon非常全面的答案,我注意到你的问题提到了在特定页面强制使用HTTPS/SSL连接。

function forceHTTPS(){
  $httpsURL = 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
  if( count( $_POST )>0 )
    die( 'Page should be accessed with HTTPS, but a POST Submission has been sent here. Adjust the form to point to '.$httpsURL );
  if( !isset( $_SERVER['HTTPS'] ) || $_SERVER['HTTPS']!=='on' ){
    if( !headers_sent() ){
      header( "Status: 301 Moved Permanently" );
      header( "Location: $httpsURL" );
      exit();
    }else{
      die( '<script type="javascript">document.location.href="'.$httpsURL.'";</script>' );
    }
  }
}

首先,在你想要通过PHP强制连接的页面靠近顶部,你可以require()一个包含此(和任何其他)自定义函数的集中式文件,然后简单地运行forceHTTPS()函数。

HTACCESS / mod_rewrite 解决方案

我个人没有实现过这种类型的解决方案(我倾向于使用像上面那样的PHP解决方案,因为它很简单),但以下方法可能是至少一个好的开始。

RewriteEngine on

# Check for POST Submission
RewriteCond %{REQUEST_METHOD} !^POST$

# Forcing HTTPS
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{SERVER_PORT} 80
# Pages to Apply
RewriteCond %{REQUEST_URI} ^something_secure [OR]
RewriteCond %{REQUEST_URI} ^something_else_secure
RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

# Forcing HTTP
RewriteCond %{HTTPS} =on [OR]
RewriteCond %{SERVER_PORT} 443
# Pages to Apply
RewriteCond %{REQUEST_URI} ^something_public [OR]
RewriteCond %{REQUEST_URI} ^something_else_public
RewriteRule .* http://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

只是出于好奇,为什么要使用 RewriteCond %{REQUEST_METHOD} !^POST$ - depoulo
13
由于重定向时不保留 POST 参数,如果您希望确保所有的 POST 提交都是安全的(任何未经安全处理的 POST 提交将被忽略),您可以省略该行。请注意,该行代码是为了增强安全性而添加的。 - Luke Stevenson
@Lucanos - 如何编写一个 RewriteCond,当 POST 时不强制重定向到 http 或 https?我的 .htaccess 在某些特定页面上强制使用 HTTPS,然后在其余页面上强制使用 HTTP。但是,在 HTTPS 页面上,有一些表单提交到我的网站根目录。表单将操作 URL 指定为 HTTPS。但是,由于网站根目录不是那些指定强制使用 HTTPS 的页面之一,因此我的 .htaccess 强制进行重定向,这意味着 POST 变量会丢失。如何防止 POST 重定向? - StackOverflowNewbie
1
@StackOverflowNewbie: RewriteCond %{REQUEST_METHOD} !^POST$ 这行代码应该可以防止 POST 请求受到重定向的影响。 - Luke Stevenson
我喜欢这个PHP版本。它更好,因为它考虑了POST并且在头部已经发送的情况下处理得更好。 - TheStoryCoder
为了更加完整,您还应该在 PHP 代码中检查原始的 POST 请求(例如 JSON 字符串)。因此,计算 POST 值的行应该像这样编写: if (count( $_POST) > 0 || file_get_contents('php://input') ) - TheStoryCoder

15

基于mod-rewrite的解决方案:

在htaccess文件中使用以下代码,可以自动将所有http请求重定向到https。

RewriteEngine on

RewriteCond %{HTTPS}::%{HTTP_HOST} ^off::(?:www\.)?(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [NE,L,R]

这将把您的非 wwwwww http请求重定向到https的www版本。

另一种解决方案(Apache 2.4 *)

RewriteEngine on

RewriteCond %{REQUEST_SCHEME}::%{HTTP_HOST} ^http::(?:www\.)?(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [NE,L,R]

由于自Apache 2.4版本以后添加了%{REQUEST_SCHEME}变量到mod-rewrite中,因此这无法在低版本的Apache上运行。


10

我想指出的是,当使用多个不同深度目录下的.htaccess文件时,Apache拥有最差的继承规则。存在两个关键问题:

  • 默认情况下,仅执行最深层次的.htaccess文件中包含的规则。您必须指定RewriteOptions InheritDownBefore指令(或类似指令)才能更改此行为。(参见问题)
  • 模式应用于相对于子目录而不是上级目录的文件路径,其中包含具有给定规则的.htaccess文件。(参见讨论)

这意味着如果您在子目录中使用其他.htaccess文件,则Apache Wiki上建议的全局解决方案不会起作用。我编写了一个修改版,它可以解决这个问题:

RewriteEngine On
# This will enable the Rewrite capabilities

RewriteOptions InheritDownBefore
# This prevents the rule from being overrided by .htaccess files in subdirectories.

RewriteCond %{HTTPS} !=on
# This checks to make sure the connection is not already HTTPS

RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [QSA,R,L]
# This rule will redirect users from their original location, to the same location but using HTTPS.
# i.e.  http://www.example.com/foo/ to https://www.example.com/foo/

4

简单易行,只需添加以下内容

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

这基本上是互联网上所有人告诉你强制使用https的方法。然而,每次我这样做,我的整个网站就会变成FORBIDDEN或者您没有权限查看之类的东西...我做错了什么?在我发帖提问之前,我只是想知道你是否有任何想法。 - ThN
我曾经遇到过类似的问题,不得不在https.conf文件中应用规则。请见下面我的回答。 - Risteard

0

这段代码对我有效

RewriteEngine On
RewriteBase /
RewriteCond %{HTTP:X-HTTPS} !1
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

-1

简单的一个:

RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www\.example\.com)(:80)? [NC]
RewriteRule ^(.*) https://example.com/$1 [R=301,L]
order deny,allow

请将您的网址替换为 example.com


不行!网站的URL需要是动态的。 - Amir Savand

-2

尝试使用这段代码,它适用于所有版本的URL:

  • website.com
  • www.website.com
  • http://website.com
  • http://www.website.com

    RewriteCond %{HTTPS} off
    RewriteCond %{HTTPS_HOST} !^www.website.com$ [NC]
    RewriteRule ^(.*)$ https://www.website.com/$1 [L,R=301]
    
  • website.com
  • www.website.com
  • http://website.com
  • http://www.website.com

    RewriteCond %{HTTPS} off
    RewriteCond %{HTTPS_HOST} !^www.website.com$ [NC]
    RewriteRule ^(.*)$ https://www.website.com/$1 [L,R=301]
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接