Laravelで作ったサービスをSSL化した時にやったことと参考記事一覧

Laravelで作ったサービスをSSL化した時に、ページによってhttpのままだったり、httpsになっているはずなのに保護された通信になってなかったりしたので実施した対策ことを記載します。

#HTTPで接続されたらHTTPSにリダイレクト
全ページHTTPSで接続されるように強制したいので、HTTPでの接続を全てHTTPSにリダイレクトします。
リダイレクトはMiddlewareで出来ます。
`$_SERVER`の中身を見てHTTPSかどうかを判別するのですが、気をつけないといけないのが、使っているWebサーバーによって`$_SERVER`のキーの内容が異なるということです。
ここは以下の記事を参考にしました。
https://qiita.com/suin/items/e5cfee2f34efedb67b8c

1.Middlewareの作成
aritisanコマンドで自動生成できます。

```
php artisan make:middleware RedirectToHttps(クラス名)
```

`App\Http\Middleware`の中に生成したファイルが作成されます。

2.RedirectToHttpsクラスの記述

```php
//App\Http\Middleware\RedirectToHttps.php
class RedirectToHttps
{
    public function handle($request, Closure $next)
    {
        //このhandleメソッドで判別
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        return $next($request);
    }

    //Webサーバー毎にキーと値で判別
    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true ) // Apache
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true ) // IIS
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true ) // Reverse proxy
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true ) // Reverse proxy
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }
}

```

3.`Kenel`に登録

```php
//App\Http\Kernel.php
protected $middleware = [
    \App\Http\Middleware\RedirectToHttps::class,
];
```

これでHTTPで接続された場合は全てHTTPSにリダイレクトされることになります。

#各ページ内のURLをHTTPSに変更
Laravelでは`url()`ヘルパ関数が使えます。
私の場合は開発を始めた段階でHTTPS接続の際に何が必要かよくわかっていなかったので、bladeテンプレート上で以下のように記述していました。

```html
<a href="{{ url('/profile'.$user->id) }}">プロフィール</a>
```

この記述の場合、実際に表示されるURLは`http://・・・`となってしまいます。
ただのリンクの場合はリダイレクトされるのでそこまで問題ないですが(それでも不要にリダイレクトさせるのはパフォーマンスに悪影響なので全URLをちゃんとhttpsにすべきだと思います)、formのaction属性の場合リダイレクトされることでpostしたデータが破棄されてしまうので致命的なエラーになってしまいます。

(※私はformのaction属性を`{{ route('/profile') }}`のように`route`ヘルパ関数を使っていたのですが、`route`の場合もhttpになってしまうため、urlヘルパ関数に変えて、以下の方法でhttpsにしました。routeヘルパでhttpsにする方法をご存知の方いらっしゃれば教えて頂けると嬉しいです。)


Laravelの`url()`ヘルパ関数は第３引数の`$secure`を設定することでhttp、httpsを切り替えることが出来ます。
`$secure`の部分がtrueの場合はhttpsで表示されます。

```php
// Illuminate\Faundation\helpers.php
function url($path = null, $parameters = [], $secure = null){・・・}
```

ただ、`$secure`に`true`と書いてしまうと、ローカルでもhttpsで表示されることになってしまいます。
ローカルではhttp、本番はhttpsで接続する必要があります。
このurlヘルパ関数は複数のビューで使っているため、全ビューで共通で使える変数を定義し、環境によってtrueとfalseが変数に格納されるようにします。

全ビュー共有出来る変数の定義は、`ServiceProvider`の`boot`メソッドで定義します。
`View`ファサードの`share`メソッドを使うことで全ビュー共通で使えるようになります。

```php
//App\Providers\AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $is_production = env('APP_ENV') === 'production' ? true : false;
        View::share('is_production',$is_production);
    }
    //以下略
}
```

あとはurlヘルパを使っている部分を以下のように書き換えていきます。

```html
<a href="{{ url('/profile',$user->id,$is_production) }}">プロフィール</a>
```

ちなみに、パラメータが２つ以上の場合は第２引数を配列の表現にします。

```html
<a href="{{ url('/profile',[$user->id,$room->id],$is_production) }}">プロフィール</a>
```
パラメータ１つでも配列で出来るかと思ったのですが、エラーになっちゃいました。

#各ページ内で読み込んでいるリソースのURLをhttpsにする

ページ自体をhttpsにした場合、例えばそのページで読み込んでいるJavascriptのURLがhttpだったらJavascriptが読み込まれずに意図した動作を実現できなくなってしまいます。

また、セキュアではないURLが残っているとhttpsで接続しているのに「保護された通信」になってくれません。

Laravelでは`asset()`を使ってblade内からCSSやJS、画像等のリソースを読み込めるのですが、この`asset()`でも第２引数をtrueにすることで読み込み先のURLをhttpsにすることが出来ます。

```html
<script src="{{ asset('js/jquery-3.2.0.min.js',$is_production) }}"></script>
```

ちなみに、`secure_asset()`を使うことで第２引数無しでhttps固定にも出来ます。
`secure_asset()`は`asset()`をラップしています。

#Controllerで`redirect()`を利用している部分をhttpsにする
Controller内で以下のようにredirect()メソッドを使ってリダイレクトの処理を記述していましたが、リダイレクト先のURLがhttpになってしまうため、先ほどのMiddlewareの影響でリダイレクト後さらにhttpsにリダイレクトする、という処理になってしまいました。

```php
return redirect()->route('profiles',[$user]);
```

`redirect()`メソッドも引数を指定することでリダイレクト先をhttpsにすることが出来ます。

```php
//App\Http\Controllers\UserController.php

//環境を判別するbool値を設定するプロパティを定義
protected $is_production;

//__construct内で環境を判別してプロパティに値をセット
$this->is_production = env('APP_ENV') === 'production' ? true : false;

// 省略

//メソッド内でリダイレクトする際に以下のように引数を設定
return redirect('/profiles/'.$user->id,302,[],$this->is_production);
```

以上です。
最初から知ってれば後から上記のような変更を行う必要がなかったと思うので、次回からはちゃんと最初から設定出来るようにしようと思います。
ちなみに、以下の記事を参考にしましたが、ヘルパ関数の引数とか動作とかは実際にソースを見て調べました。

(参考記事)
[https(SSL)通信の環境下でjavascriptが動かなくなる場合の原因と解決方法](https://www.webernote.net/webcreate/https-javascript.html)
[Laravel5で.htaccessを使用せず常時SSL化対応する方法](https://qiita.com/qwe001/items/7cd0bcb149b5b5cc0fd7)
[is_ssl() : HTTPSプロトコルかどうかを返す](https://qiita.com/suin/items/e5cfee2f34efedb67b8c)
[Laravelのbladeで画像表示したいがページによって表示されたりされなかったりする](https://teratail.com/questions/49552)
[SSLでLaravel5.1のヘルパー関数 url() を使う](https://qiita.com/yyano/items/ff7f1fd80794a3db848e)
[Laravel5.3でSSL通信を強制する](https://qiita.com/sawadashota/items/2f1c7b1e2905519c4e6a)
[SSL化しているのに”保護された通信”と表示されない時に調べること](https://kozenikasegi.com/life/pc-smartphone/check-ssl/)