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

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

まず、前回のようにアップロードをパブリックの場所に保存して、特定のユーザーだけに表示のための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');
	});
});

By khino