前回は、画像をパブリックに表示する方法を説明しましたが、今回は画像をプライベートに表示する方法です。
いくつか方法があります。
まず、前回のようにアップロードをパブリックの場所に保存して、特定のユーザーだけに表示のためのURLを教える。
しかし、DBから自動発行されるproduct_image_idを画像ファイル名に使用するなら、URLを操作することで他のファイルも見れてしまいます。
そうなら、画像のURLをわかりにくいように変えて、他の画像のURLを予想しにくくすることも可能です。
例えば、235.jpgとは見せずに、1f3870be274f6c49b3e31a0c6728957f.jpgにするとか。
それは、md5()を利用することで簡単に可能です。
public function filename()
{
$ext = 'jpg';
switch($this->mime)
{
case 'image/jpeg':
case 'image/jpg':
$ext = "jpg";
break;
case 'image/png':
$ext = "png";
break;
case 'image/gif':
$ext = "gif";
break;
}
return sprintf("%d.%s", md5($this->product_image_id), $ext);
}
よりセキュアにするには、DBに保存するときに、uniqid()あるいは、openssl-random-pseudo-bytes()を使用してランダムな値を生成して、その値をファイル名として保存するとか。要するに、IDのように連続な番号とはならないので、容易にファイル名を予測できないようにすることです。
しかし、究極は、ファイルをパブリックから見れない場所に保存して、それを表示する方法です。
例えば、storage/images/product/1.jpgのように、パブリックから見れないstorageのディレクトリに画像をアップロードするようにして、見せるときには、ログインしたユーザーと関連ある画像だけを、そのユーザーに表示する。
この場合は、パブリックに保存されている画像と違い、固定のURLを通してウェブサーバーに画像の表示を任せることはできません。逆に、あたかもウェブサーバーが画像ファイルを読んでデータをストリームするという作業と同じことをプログラムで行います。header()を使用すれば、そう難しいことではありません。
namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use Log;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Product;
use App\ProductImage;
class ProductController extends Controller
{
public function getImage(Product $product)
{
return view('user/product_image', compact('product'));
}
public function downloadImage(ProductImage $product_image)
{
//@TODO ここで、認証したユーザーに画像を表示していいかどうかをチェック。
//そうでないなら、空の画像を表示
$filename = $product_image->filename();
header("Content-type: $product_image->mime name=$filename");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Length: ".@filesize($product_image->path));
header("Expires: 0");
@readfile($product_image->path);
exit;
}
}
上で使用されるテンプレートは、
@extends('user.layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">アップロードしたファイルを表示</div>
<div class="panel-body">
<div>
@foreach ($product->product_images as $image)
<img src="{{ url('/user/product_image', $image->product_image_id) }}">
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
のようになります。
routes.phpは、以下のように認証保護された中でコールされます。
Route::group(['prefix' => 'user', 'middleware' => 'web'], function () {
Route::get('login', 'User\Auth\AuthController@showLoginForm');
Route::post('login', 'User\Auth\AuthController@login');
Route::get('logout', 'User\Auth\AuthController@logout');
..
Route::group(['middleware' => 'auth:user' ], function () {
Route::get('home', 'User\HomeController@index');
..
Route::get('product/{product}/image', 'User\ProductController@getImage');
Route::get('product_image/{product_image}', 'User\ProductController@downloadImage');
});
});
メルマガ購読の申し込みはこちらから。
