前回は、画像をパブリックに表示する方法を説明しましたが、今回は画像をプライベートに表示する方法です。

いくつか方法があります。

まず、前回のようにアップロードをパブリックの場所に保存して、特定のユーザーだけに表示のための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()を使用すれば、そう難しいことではありません。

ProductController.php
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;
    }
}

上で使用されるテンプレートは、

user/product_image.blade.php
@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');
    });
});
メルマガ購読の申し込みはこちらから。

By khino