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クラスの記述
//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
に登録
//App\Http\Kernel.php
protected $middleware = [
\App\Http\Middleware\RedirectToHttps::class,
];
これでHTTPで接続された場合は全てHTTPSにリダイレクトされることになります。
#各ページ内のURLをHTTPSに変更
Laravelではurl()
ヘルパ関数が使えます。
私の場合は開発を始めた段階でHTTPS接続の際に何が必要かよくわかっていなかったので、bladeテンプレート上で以下のように記述していました。
<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()
ヘルパ関数は第3引数の$secure
を設定することでhttp、httpsを切り替えることが出来ます。
$secure
の部分がtrueの場合はhttpsで表示されます。
// Illuminate\Faundation\helpers.php
function url($path = null, $parameters = [], $secure = null){・・・}
ただ、$secure
にtrue
と書いてしまうと、ローカルでもhttpsで表示されることになってしまいます。
ローカルではhttp、本番はhttpsで接続する必要があります。
このurlヘルパ関数は複数のビューで使っているため、全ビューで共通で使える変数を定義し、環境によってtrueとfalseが変数に格納されるようにします。
全ビュー共有出来る変数の定義は、ServiceProvider
のboot
メソッドで定義します。
View
ファサードのshare
メソッドを使うことで全ビュー共通で使えるようになります。
//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ヘルパを使っている部分を以下のように書き換えていきます。
<a href="{{ url('/profile',$user->id,$is_production) }}">プロフィール</a>
ちなみに、パラメータが2つ以上の場合は第2引数を配列の表現にします。
<a href="{{ url('/profile',[$user->id,$room->id],$is_production) }}">プロフィール</a>
パラメータ1つでも配列で出来るかと思ったのですが、エラーになっちゃいました。
#各ページ内で読み込んでいるリソースのURLをhttpsにする
ページ自体をhttpsにした場合、例えばそのページで読み込んでいるJavascriptのURLがhttpだったらJavascriptが読み込まれずに意図した動作を実現できなくなってしまいます。
また、セキュアではないURLが残っているとhttpsで接続しているのに「保護された通信」になってくれません。
Laravelではasset()
を使ってblade内からCSSやJS、画像等のリソースを読み込めるのですが、このasset()
でも第2引数をtrueにすることで読み込み先のURLをhttpsにすることが出来ます。
<script src="{{ asset('js/jquery-3.2.0.min.js',$is_production) }}"></script>
ちなみに、secure_asset()
を使うことで第2引数無しでhttps固定にも出来ます。
secure_asset()
はasset()
をラップしています。
#Controllerでredirect()
を利用している部分をhttpsにする
Controller内で以下のようにredirect()メソッドを使ってリダイレクトの処理を記述していましたが、リダイレクト先のURLがhttpになってしまうため、先ほどのMiddlewareの影響でリダイレクト後さらにhttpsにリダイレクトする、という処理になってしまいました。
return redirect()->route('profiles',[$user]);
redirect()
メソッドも引数を指定することでリダイレクト先をhttpsにすることが出来ます。
//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が動かなくなる場合の原因と解決方法
Laravel5で.htaccessを使用せず常時SSL化対応する方法
is_ssl() : HTTPSプロトコルかどうかを返す
Laravelのbladeで画像表示したいがページによって表示されたりされなかったりする
SSLでLaravel5.1のヘルパー関数 url() を使う
Laravel5.3でSSL通信を強制する
SSL化しているのに”保護された通信”と表示されない時に調べること