SSL
laravel5
laravel5.4

Laravel5で.htaccessを使用せず常時SSL化対応する方法

More than 1 year has passed since last update.

はじめに

普通、全ページをSSL化(https)にするなら.htaccessにこんな風に書きますよね。

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

静的に作ってるページしかないならこれだけで全部httpsにリダイレクトするので、なんだかよさそうな気がします。
しかし、この書き方はformが絡むと結構深刻な問題を引き起こします。

Laravelで簡単なお問い合わせフォームを作ったとして、次のような書き方をしていた場合

{{ Form::open(['url' => route('inquiry.store'), 'method' => 'POST']) }}
    {{ csrf_token() }}
    {{ Form::text('name', NULL) }}
    {{ Form::submit('submit') }}
{{ Form::close() }}

実際には次のようなHTMLを出力します。

<form method="POST" action="http://www.example.com/inquiry/store">
    <input name="_token" type="hidden" value="*******">
    <input name="name" type="text" value=""></td>
    <input type="submit" value="submit">
</form>

.htaccessで記述した場合の問題点

注目すべきはaction属性の値。httpになっています。
Laravelのrouteやurlヘルパーの書き方は標準だと、httpから始まるURLを出力します。

POSTの値はリダイレクトを挟むと消失するので、もし.htaccessでリダイレクトさせたら、
お問い合わせフォームから問い合わせてもSSL対応完了後は一件も問い合わせがこなくなった...
場合によっては500エラー(Non Object Error)になってエラーログが出まくる可能性も...
っていうことがあり得てしまいます。

なので、HTTPからのアクセスはHTTPSにリダイレクトさせつつ、routeやurlヘルパーで書いてきて、
今までhttpとして出力されていた文字列もhttpsとして出力して欲しいですよね?
そんなムシのいい話を、Laravelは実現してくれます。

実装

Middleware
php artisan make:middleware ForceHttpProtocol
/app/Http/Middleware/ForceHttpProtocol.php
<?php

namespace App\Http\Middleware;
use Closure;

class ForceHttpProtocol {

    public function handle($request, Closure $next) {
        if (!$request->secure() && env('APP_ENV') === 'production') { // 本番環境のみ常時SSL化する
            return redirect()->secure($request->getRequestUri());
        }

        return $next($request);
    }

}
Kernel

必要箇所以外の記述は省略します

/app/Http/Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \App\Http\Middleware\ForceHttpProtocol::class, // 追加
    ];

特定ページのみSSL対応

サーバー証明書のコストの関係とかで、常時SSL化対応ではなく、特定のページだけSSL化することになった場合は次のようにします。

Kernel
/app/Http/Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'forceSsl' => App\Http\Middleware\ForceHttpProtocol::class, // 追加
    ];
Route
/routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('/', 'HomeController@index'); // http

Route::group(['prefix' => 'inquiry', 'as' => 'inquiry', 'middleware' => 'forceSsl'], function(){ // https
    Route::get('/', 'InquiryController@index');
    Route::post('/store', 'InquiryController@store');
    Route::get('/finish', 'InquiryController@finish');
});

追記:301リダイレクトさせる方法

なんか結構このページが見られているようなので、追記します。
上に書いたhttpsリダイレクトは、302リダイレクトになっています。

なぜなら、secureメソッドが送るデフォルトのステータスコードが302だからです。

/vendor/laravel/framework/src/Illuminate/Routing/Redirector.php
    /**
     * Create a new redirect response to the given HTTPS path.
     *
     * @param  string  $path
     * @param  int     $status
     * @param  array   $headers
     * @return \Illuminate\Http\RedirectResponse
     */
    public function secure($path, $status = 302, $headers = [])
    {
        return $this->to($path, $status, $headers, true);
    }

301リダイレクトさせたいならば、下のように、第二引数に301と明示的に書けばそれでOKです。

/app/Http/Middleware/ForceHttpProtocol.php
<?php

namespace App\Http\Middleware;
use Closure;

class ForceHttpProtocol {

    public function handle($request, Closure $next) {
        if (!$request->secure() && env('APP_ENV') === 'production') {
            return redirect()->secure($request->getRequestUri(), 301); // ← ここを変更しました
        }

        return $next($request);
    }

}

当たり前すぎるのか、誰も書いてなかったので。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away