Laravel 5 - 重定向到 HTTPS

145

我正在进行我的第一个 Laravel 5 项目,但不确定在哪里或如何放置逻辑来强制应用程序使用 HTTPS。这里的关键是有许多域名指向该应用程序,其中只有三个中的两个使用 SSL(第三个是备用域名,长话短说)。因此,我希望在我的应用程序逻辑中处理这个问题,而不是在 .htaccess 文件中处理。

在 Laravel 4.2 中,我使用以下代码在 filters.php 中实现了重定向:

App::before(function($request)
{
    if( ! Request::secure())
    {
        return Redirect::secure(Request::path());
    }
});

我认为中间件是应该实现这种功能的地方,但我无法确定如何使用它来解决问题。

谢谢!

更新

如果您像我一样使用Cloudflare,则可以通过在控制面板中添加新的页面规则来实现此操作。


第三个域名会发生什么?如果你在所有路由上强制使用https,第三个域名还能继续工作吗? - Laurence
使用 $_SERVER['HTTP_HOST'] 检测。 - NightMICU
Cloudflare页面规则生效需要多长时间? - CodeGuru
哦,我得在 DNS 设置中启用代理,哈哈! - CodeGuru
24个回答

286

你可以使用中间件类来实现它。让我给你一个想法。

namespace MyApp\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\App;

class HttpsProtocol {

    public function handle($request, Closure $next)
    {
            if (!$request->secure() && App::environment() === 'production') {
                return redirect()->secure($request->getRequestUri());
            }

            return $next($request); 
    }
}

然后将此中间件应用于每个请求,在Kernel.php文件中设置规则,如下所示:

protected $middleware = [
    'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
    'Illuminate\Cookie\Middleware\EncryptCookies',
    'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
    'Illuminate\Session\Middleware\StartSession',
    'Illuminate\View\Middleware\ShareErrorsFromSession',

    // appending custom middleware 
    'MyApp\Http\Middleware\HttpsProtocol'       

];

在上述示例中,如果满足以下条件,则中间件将重定向到 https:

  1. 当前请求不带安全协议(http)。
  2. 您的环境等于production。因此,请根据您的偏好调整设置。

Cloudflare

我正在使用具有通配符 SSL 的生产环境中的此代码,并且该代码可以正常工作。如果我删除&& App::environment() === 'production'并在本地主机上测试它,则重定向也能正常工作。因此,安装或未安装 SSL 不是问题。看起来您需要非常注意 Cloudflare 层,以便被重定向到 Https 协议。

编辑于 2015 年 3 月 23 日

感谢 @Adam Link 的建议:这可能是由 Cloudflare 传递的标头引起的。CloudFlare 可能会通过 HTTP 点击您的服务器,并传递一个 X-Forwarded-Proto 标头,声明它正在转发 HTTPS 请求。您需要在中间件中添加另一行代码,如下所示...

$request->setTrustedProxies( [ $request->getClientIp() ] ); 

相信CloudFlare发送的头信息,这将停止重定向循环。

编辑 2016年9月27日 - Laravel v5.3

只需将中间件类添加到kernel.php文件中的web组中:

protected $middlewareGroups = [
    'web' => [
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,

        // here
        \MyApp\Http\Middleware\HttpsProtocol::class

    ],
];
记住,默认情况下每个路由都应用了“web”组,因此您不需要在路由或控制器中显式设置“web”。

编辑 23/08/2018 - Laravel v5.7

  • 要根据环境重定向请求,可以使用App::environment() === 'production'。对于以前的版本是env('APP_ENV') === 'production'
  • 实际上,使用\URL::forceScheme('https');并不会重定向。它只是在网站呈现后使用https://构建链接。

7
这似乎给了我一个重定向循环......看起来应该能工作,但我不知道是否有任何区别,因为我们正在使用Cloudflare SSL。但我不认为这会改变简单的重定向。 - NightMICU
4
@NightMICU,我不确定您是否解决了重定向问题,但很可能是由Cloudflare传递的头部引起的。Cloudflare可能通过HTTP命中您的服务器,并传递一个X-Forwarded-Proto头部,声明它正在转发一个HTTPS请求。您需要在您的中间件中添加另一行代码,即 $request->setTrustedProxies( [ $request->getClientIp() ] ); 来信任Cloudflare发送的头部。这将停止重定向循环。 - Adam Link
2
@manix 太棒了。我自己的项目在上周末也遇到了这个HTTPS问题 - 这些小细节会让你沮丧数小时! - Adam Link
9
非常好的回答!只有一个细节:最好使用301重定向来告诉Google这是一个永久性的移动。例如:return redirect()->secure($request->getRequestUri(), 301); - adriaroca
4
对于那些使用负载均衡器或代理的人,可以将 secure() 改为 $request->server('HTTP_X_FORWARDED_PROTO') != 'https',这对我有效。 - Shiro
显示剩余19条评论

77

对我有用的另一种选项,在AppServiceProvider中将此代码放置在boot方法中:

\URL::forceScheme('https');

在执行forceSchema('https')之前编写的函数是错误的,正确名称为forceScheme。


16
嗨,我通过谷歌搜索找到了这个内容 - 请注意,在5.4中,它是\URL :: forceScheme('https'); - dev
8
在同一个文件中,您还可以进行以下操作:如果($this->app->environment() === 'production') { $this->app['request']->server->set('HTTPS', true); }。 - Rory
2
你是不是想说 \URL::forceScheme('https') - Ernest Okot
22
我很确定这只是用于构建链接。它不会强制用户使用https,只会提供以" https://"为前缀的链接。 - Weston Watson
@Rory,只有你的解决方案对我有效。非常感谢! - Murat Colyaran
显示剩余3条评论

42

另外,如果您使用Apache,则可以使用.htaccess文件来强制使用https前缀的URL。在Laravel 5.4上,我将以下行添加到我的.htaccess文件中,这对我有效。

RewriteEngine On

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

3
如果您有多个环境(开发、测试、生产),那么需要在所有环境中设置SSL,否则会出现问题。 - Mladen Janjetovic
@MladenJanjetovic,你可以在开发环境中使用RewriteCond %{HTTP_HOST} !=localhost来解决这个问题。 - Dan
4
@Dan - 是的,但你仍然需要为舞台和本地设置它(如果开发人员在本地开发中使用不同的URL,如.dev、.local、子域名等,则这更加复杂)。我更喜欢在应用程序中包含那种逻辑。 - Mladen Janjetovic
这应该是正确的答案。如果重写规则在TLS代理后面有影响,则无法测试它,但这应该由Web服务器完成,而不是PHP代码。 - Bartłomiej Sobieszek

24

对于 Laravel 5.4,请使用此格式来获取 HTTPS 重定向,而不是使用 .htaccess。

namespace App\Providers;

use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        URL::forceScheme('https');
    }
}

16
澄清一下:1)这些更改应该在app / Providers / AppServiceProvider.php中完成;2)这只是为了设置应用程序内生成的链接使用SSL,它不会强制您使用SSL。 - phoenix
嗨,如果我点击一个按钮以进入下一个路由,这种方法不会生成路由,也不会给我404错误。 - Saket Sinha
1
这不是一个https重定向。但它允许提供https://网站。如果您将其更改为http://,它也可以提供服务。 - Olu Adeyemo

14

类似于manix的答案,但只有一个地方不同。强制使用HTTPS的中间件

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

class ForceHttps
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (!app()->environment('local')) {
            // for Proxies
            Request::setTrustedProxies([$request->getClientIp()], 
                Request::HEADER_X_FORWARDED_ALL);

            if (!$request->isSecure()) {
                return redirect()->secure($request->getRequestUri());
            }
        }

        return $next($request);
    }
}

请求(Request)需要是静态的吗? - Soma Hesk
1
@jRhesk,它可能不行,但请尝试并随意修改答案。 - Mladen Janjetovic
1
在 Laravel 5.6 中,使用 Request::setTrustedProxies 需要传递第二个参数。Request::setTrustedProxies([$request->getClientIp()], Request::HEADER_X_FORWARDED_ALL); - Emanuel A.

14

仅使用 .htaccess 文件来实现 https 重定向呢?将其放置在项目根目录下(而不是公共文件夹中)。您的服务器需要配置为指向项目根目录。

<IfModule mod_rewrite.c>
   RewriteEngine On
   # Force SSL
   RewriteCond %{HTTPS} !=on
   RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
   # Remove public folder form URL
   RewriteRule ^(.*)$ public/$1 [L]
</IfModule>

我在写下这篇回答时使用的是 Laravel 5.4(最新版本),但即使 Laravel 更改或删除了某些功能,它仍应该适用于未来的版本。


Chrome 给我一个错误:Too many redirects。看起来像是出现了一个循环。 - phoenix
嗨,我已经更新了答案。请确保将此.htaccess文件放在项目根目录中,并将您的服务器(apache配置)指向项目根目录。 - Maulik
1
@MladenJanjetovic 你可以为这些环境拥有不同的htaccess文件。 - Burgi
1
@MladenJanjetovic 在应用程序中拥有它肯定有其优点,但从效率和速度的角度来看,我认为将其放在服务器配置中是有优势的,这样您就不必加载 Laravel 来进行重定向。通过使用重写条件来检查域名,可以在单个版本化的 .htaccess 中实现特定于环境的配置,例如 RewriteCond %{HTTP_HOST} productiondomain\.com$ [NC] - Chris
1
我也更喜欢将这个放在根目录的 .htaccess 文件中,正如 Crhis 所说,环境特定的设置可以在这里完成,尽管比 Mladen 的解决方案稍微不够优雅。 - sveti petar
显示剩余2条评论

9
这是针对Laravel 5.2.x及更高版本的内容。如果您想要选择在HTTPS上提供某些内容并在HTTP上提供其他内容,这里有一个对我有效的解决方案。您可能会想,为什么有人只想在HTTPS上提供部分内容?为什么不全部使用HTTPS呢?
虽然完全可以在HTTPS上提供整个站点,但在服务器上全面使用HTTPS会增加额外的开销。请记住,加密并不便宜。稍微增加的开销也会影响应用程序的响应时间。您可能会说,商品硬件很便宜,影响可以忽略不计,但我偏离了主题:)我不喜欢在HTTPS上提供大量包含图片等营销内容的页面。所以这里是解决方案。它类似于其他人建议的中间件,但它是一个完整的解决方案,允许您在HTTP / HTTPS之间切换。
首先创建一个中间件。
php artisan make:middleware ForceSSL

这是您的中间件应该如何看起来:

<?php

namespace App\Http\Middleware;

use Closure;

class ForceSSL
{

    public function handle($request, Closure $next)
    {

        if (!$request->secure()) {
            return redirect()->secure($request->getRequestUri());
        }

        return $next($request);
    }
}

请注意,我不基于环境进行过滤,因为我已经为本地开发和生产设置了HTTPS,所以没有必要这样做。
请将以下内容添加到 \App\Http\Kernel.php 的 routeMiddleware 中,这样您就可以选择哪个路由组应该强制使用SSL。
    protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'forceSSL' => \App\Http\Middleware\ForceSSL::class,
];

接下来,我想要保护两个基本的组:登录/注册等以及所有其他需要在Auth中间件后才能访问的内容。

Route::group(array('middleware' => 'forceSSL'), function() {
/*user auth*/
Route::get('login', 'AuthController@showLogin');
Route::post('login', 'AuthController@doLogin');

// Password reset routes...
Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('password/reset', 'Auth\PasswordController@postReset');

//other routes like signup etc

});


Route::group(['middleware' => ['auth','forceSSL']], function()
 {
Route::get('dashboard', function(){
    return view('app.dashboard');
});
Route::get('logout', 'AuthController@doLogout');

//other routes for your application
});

请从控制台确认您的中间件是否正确应用于路由。

php artisan route:list

现在,您已经保护好了应用程序的所有表单或敏感区域,关键是使用视图模板来定义安全链接和公共(非 HTTPS)链接。
基于以上示例,您应该将安全链接呈现如下 -
<a href="{{secure_url('/login')}}">Login</a>
<a href="{{secure_url('/signup')}}">SignUp</a>

非安全链接可以被渲染为
<a href="{{url('/aboutus',[],false)}}">About US</a></li>
<a href="{{url('/promotion',[],false)}}">Get the deal now!</a></li>

这段代码的作用是将完全合格的URL进行渲染,比如https://yourhost/loginhttp://yourhost/aboutus
如果您没有使用http来呈现完全合格的URL并使用相对链接url('/aboutus'),那么在用户访问安全站点后,https会继续存在。
希望这可以帮助你!

7

您可以使用RewriteRule在.htaccess文件中的同一文件夹中强制使用SSL,与您的index.php文件相同。


5
在Laravel 5.1中,我使用了: 文件:app\Providers\AppServiceProvider.php
public function boot()
{
    if ($this->isSecure()) {
        \URL::forceSchema('https');
    }
}

public function isSecure()
{
    $isSecure = false;
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
        $isSecure = true;
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
        $isSecure = true;
    }

    return $isSecure;
}

注意:使用forceSchema,而不是forceScheme

4

最简单的方法是在应用程序层面上完成。在文件中:

app/Providers/AppServiceProvider.php

请添加以下内容:
use Illuminate\Support\Facades\URL;

在boot()方法中添加以下内容:

$this->app['request']->server->set('HTTPS', true);
URL::forceScheme('https');

这将在应用程序级别将所有请求重定向到https。
(注意:这已在laravel 5.5 LTS上进行了测试)

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