すぐできる!Laravelで画像の直リンクを禁止する

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、この間 意外と簡単!Laravelでgifアニメをつくる など、いくつか画像に関連する記事を公開しました。

そして、それらの記事は画像自体をつくったり投稿したりするという内容だったのですが、今回は少し違った角度から画像に関する機能を実装していみたいと思います。

具体的に何かというと・・・

直リンクを禁止する方法

です。

直リンクとは、他人のサイトにある画像を自分のサイトで表示することで、公開している側からするとサーバーへの負荷が増えるなど、あまり嬉しいことではなかったりします。

そこで!

今回はLaravelを使って画像の直リンクを禁止する機能を実装してみます。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Laravel 6.x

どのようにして直リンク禁止の画像を実装をするか

直リンクを完全に禁止するには、まず.htaccessなどでリファラ(呼び出し元のURL)をチェックする方法が考えられますが、リファラは比較的簡単に偽装できるため、今回は少し考え方を変えて、「ごくごく短い有効期限があるURL」をつくることで実装します。

つまり、画像URLのパラメータに

  • 有効期限(例えば、1分間後まで有効)
  • その有効期限が正しいものなのかをチェックするワンタイムパスワード

をつけるという方法になります。

(URLの例)

http://******/item_image/1?expiration=*****&token=******

そして、このパラメータは自分のサイト内では随時更新することができるのでいつでも画像が表示されますが、直リンクの場合はすぐに有効期限がきれてしまうので、それ以降は画像が表示されることはなくなる、というわけですね。

では実際に作業をしていきましょう!

商品画像を用意する

今回はテストとして、私が最近大ファンになった「六甲ビールIPA・スサノオ」の画像を使って実装していきます。(個人的にトップ3に入っています🍺✨)

Laravel/storage/app/images/itemsというフォルダをつくり、この中へ画像を設置しておいてください。

ルートをつくる

Route::get('item_image/{id}', 'ImageController@item_image');

{id}の部分が可変で、数字が入ってきます。

モデルをつくる

画像のURLを簡単に取得できるようにモデルをつくっておきましょう。(ただし、今回はテストなのでDBは使いません。皆さんの状況に合わせてマイグレーションなども作ってください)

php artisan make:model Item

そして、中身を以下のようにします。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Item extends Model
{
    public static function url($path) { // 直リンク禁止画像のURLをつくる

        $expiration = now()->addMinute()->format('U');   // 1分間だけ有効
        $token = encrypt($expiration);
        return url($path) .
            '?expiration='. $expiration .
            '&token='. $token;

    }
}

重要なのは、encrypt()の部分です。ここでは、Laravelの暗号機能を使って有効期限を暗号化し、ワンタイムパスワードをつくっています。そして、後ほどこれらのデータをつかって復号し、expirationの中身が正しいかをチェックすることになります。

コントローラーをつくる

続いてコントローラーです。
以下のコマンドを実行してください。

php artisan make:controller ImageController

すると、app/Http/Controllers/ImageController.phpというファイルが作成されるので、中身を以下のように変更してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ImageController extends Controller
{
    public function __construct()
    {
        $this->middleware('protect_image');
    }

    public function item_image($id) {

        $image_path = storage_path('app/images/items/'. $id .'.jpg');

        if(!file_exists($image_path)) { // ファイルが存在しないとき

            abort(404);

        }

        return response()->file($image_path);

    }
}

まずitem_image()では画像の存在をチェックし、もしあれば画像データを返すというシンプルなものになっています。

そして、重要なのは__construct()内でミドルウェアを指定している部分です。このミドルウェアは次の項目でつくりますが、ここで画像を表示すべきか拒否すべきかをチェックすることになります。

ミドルウェアをつくる

では、先ほどコントローラーの__construcct()内で設定したミドルウェアをつくっていきましょう。

php artisan make:middleware ProtectImage

すると、app/Http/Middleware/ProtectImage.phpというファイルが作成されるので、中身を以下のようにしてください。

<?php

namespace App\Http\Middleware;

use Carbon\Carbon;
use Closure;

class ProtectImage
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $expiration = $request->expiration;
        $expiration_dt = Carbon::createFromTimestamp($request->expiration);
        $token = $request->token;

        if($expiration_dt->isFuture() && decrypt($token) === $expiration) {

            return $next($request);

        }

        abort(410, '有効期限切れです');
    }
}

そして、ミドルウェアが作成できたら、app/Http/Kernel.php$routeMiddlewareに登録しておいてください。

<?php

namespace App\Http;

use App\Http\Middleware\ProtectImage;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // 省略

    protected $routeMiddleware = [

        // 省略

        'protect_image' => ProtectImage::class
    ];

これで作業自体は完了です。
お疲れ様でした😊✨

テストしてみる

では、実際に画像が表示されるかをチェックし、さらにそのURLに1分後アクセスして有効期限切れになっているかもチェックしてみましょう。

まずは表示するためのビューです。
resources/views/item_image.blade.phpというファイルを作成して中身を以下のようにしてください。

<html>
<body>
    <div>
        <img src="{{ \App\Item::url('item_image/1') }}">
    </div>
</body>
</html>

そして、そのビューをroutes/web.phpにルートとして登録してください。

Route::get('item_image', function(){

    return view('item_image');

});

では、この状態でhttp://******/item_imageにアクセスしてみましょう。

うまく表示されています。
では、この画像のURLをコピーして1分後にアクセスしてみます。

 

(1分後・・・⏳)

 

F5キーを押してリロードします。

はい!
うまく有効期限切れの判断ができました。

成功です😊✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はLaravelで画像の直リンクを禁止する方法をお届けしました。

厳密には、表示後に特定の期間(今回は1分)は直リンクをブロックすることはできませんが、すごく短い時間だけなので直リンクを貼る側からすると、あまりメリットがなくなり結果直リンクを減らすことができるんじゃないでしょうか。

ぜひ皆さんのサイトでも活用してみてくださいね。

ではでは〜!

このエントリーをはてなブックマークに追加       follow us in feedly