How to deal with private images in laravel 5?

后端 未结 4 1688
暖寄归人
暖寄归人 2020-12-14 10:32

I am new to Laravel and trying to store private images so that only authenticated users can access them. Firstly I stored images in Public/UserImages folder. But here all th

相关标签:
4条回答
  • 2020-12-14 10:55

    Here how to do it in Laravel 5.7

    To have private files (images), you need to serve the files through a route => controller flow. And your auth middleware will handle authentication, and permission. If further authorization are needed you handle it in the controller.

    So first we set a route:

    Here we can have one route that handle all our files [i personally don't prefer that]. We can do that using such a route (it's like a wildcard).

    Route::get('/storage/{filePath}', 'FileController@fileStorageServe')
    ->where(['filePath' => '.*'])
    

    You can name it too like that:

    Route::get('/storage/{fileName}', 'FileController@fileStorageServe')
    ->where(['fileName' => '.*'])->name('storage.gallery.file');
    

    Otherwise we create a route for each type/category of files: (advantage: you will be able to control better the accessibility. (Each route and type of resources and it's rules. If you want to achieve that with the wild card route (let me call it that) you need to have conditional blocks (if else, handling all different situations. It's unnecessary operations [going directly to the right block, when the routes are separate is better, plus out of that, it allow you to organize better the permissions handling]).

    Route::get('/storage/gallery/{file}', 'System\FileController@getGalleryImage')
    ->name('storage.gallery.image');
    

     We had our routes set NOW Controller/Controllers

    The wild card one

      <?php
         public function fileStorageServe($file) {
                    // know you can have a mapping so you dont keep the sme names as in local (you can not precsise the same structor as the storage, you can do anything)
    
                    // any permission handling or anything else
    
                    // we check for the existing of the file 
                    if (!Storage::disk('local')->exists($filePath)){ // note that disk()->exists() expect a relative path, from your disk root path. so in our example we pass directly the path (/.../laravelProject/storage/app) is the default one (referenced with the helper storage_path('app')
                        abort('404'); // we redirect to 404 page if it doesn't exist
                    } 
                //file exist let serve it 
    
    // if there is parameters [you can change the files, depending on them. ex serving different content to different regions, or to mobile and desktop ...etc] // repetitive things can be handled through helpers [make helpers]
    
                    return response()->file(storage_path('app'.DIRECTORY_SEPARATOR.($filePath))); // the response()->file() will add the necessary headers in our place (no headers are needed to be provided for images (it's done automatically) expected hearder is of form => ['Content-Type' => 'image/png'];
    
    // big note here don't use Storage::url() // it's not working correctly.  
                }
    

    The per route one

    (big difference, is the parameter, now it reference just the file name, and not the relative path to storage disk root)

    <?php
    public function getCompaniesLogo($file) {
        // know you can have a mapping so you dont keep the sme names as in local (you can not precsise the same structor as the storage, you can do anything)
    
        // any permission handling or anything else
    
        $filePath =  config('fs.gallery').DIRECTORY_SEPARATOR.$file; // here in place of just using 'gallery', i'm setting it in a config file
    
        // here i'm getting only the path from the root  (this way we can change the root later) / also we can change the structor on the store itself, change in one place config.fs.
    
        // $filePath = Storage::url($file); <== this doesn't work don't use
    
         // check for existance
        if (!Storage::disk('local')->exists($file)){ // as mentionned precise relatively to storage disk root (this one work well not like Storage:url()
              abort('404');
        } 
    
        // if there is parameters [you can change the files, depending on them. ex serving different content to different regions, or to mobile and desktop ...etc] // repetitive things can be handled through helpers [make helpers]
    
        return response()->file(storage_path('app'.DIRECTORY_SEPARATOR.($file))); // the response()->file() will add the necessary headers in our place
    }
    

    Now you can check by forming the correct url (go to storage copy past the file name, and form your route. it should show you the image)

    One last thing left:

    How to show that in view

    the wild card one

    <img src="{{route('routeName', ['fileParam' => $storageRelativePath])}}" />
    

    Note that routeName here in the example above will be storage.file, and fileParam would be filePath. $storageRelativePath for example you get from the db (generally that's what it will be).

    The per route

    <img src="{{route('routeName', ['fileName' => basename($storageRelativePath)])}}" />
    

    Same but we provide only the filename.

    Notes: The best way to send such a response, is using response()->file();. That what you will find in the 5.7 doc. That performance wise against Image::make($storagePath)->response();. Unless you need to modify it in the fly.

    You can check my article in medium: https://medium.com/@allalmohamedlamine/how-to-serve-images-and-files-privatly-in-laravel-5-7-a4b469f0f706

    0 讨论(0)
  • 2020-12-14 10:56

    Following is how I solved the problem of storing images in Laravel 5 such that only authenticated users can view the images. People who are not authenticated will be directed to a login page. My server is a Ubuntu/Apache2 server.

    1. Create the directory /var/www/YOURWEBSITE/app/Assets/Images

    2. Add route to app/Http/routes.php.

      Route::get('/images/{file}','ImageController@getImage');

    3. Create a controller app/Http/Controllers/ImageController.php

      <?php
      namespace App\Http\Controllers;
      
      use App\Http\Requests;
      
      use App\Http\Controllers\Controller;
      
      use Illuminate\Http\Request;
      
      use Auth;
      
      class ImageController extends Controller {
      
          public function __construct()
         {
              $this->middleware('auth');
         } 
          public function getImage($filename) {
             $path = '/var/www/YOURWEBSITE/app/Assets/Images/'.$filename;
             $type = "image/jpeg";
             header('Content-Type:'.$type);
             header('Content-Length: ' . filesize($path));
             readfile($path);
      
          }
      
       }
      
    4. In your view you have img tags which have:

      src="{{ url('/images/test.jpg') }}"
      

    This of course assumes test.jpg is a file in /var/www/YOURWEBSITE/app/Assets/Images/

    You can of course add more logic such as not hardcoding the path of the images, etc. This is just a simple example to enforce authentication. Note the use of middleware('auth') in the controller constructor.

    0 讨论(0)
  • 2020-12-14 11:03

    I got the same issue some days ago and came up with this solution:

    1. First thing you have to do is upload the file to a non-public directory. My app is storing scanned invoices, so I'm going to place them inside storage/app/invoices. The code for uploading the file and generating the url would be:

      // This goes inside your controller method handling the POST request.
      
      $path = $request->file('invoice')->store('invoices');
      $url = env('APP_URL') . Illuminate\Support\Facades\Storage::url($path);
      

      The url returned should result in something like http://yourdomain.com/storage/invoices/uniquefilename.jpg

    2. Now you have to create a controller that uses the auth middleware to ensure the user is authenticated. Then, define a method that grabs the file from the private directory and returns it as a file response. That would be:

      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Support\Facades\Storage;
      
      class FileController extends Controller
      {
      
          public function __construct()
          {
              $this->middleware('auth');
          }
      
          public function __invoke($file_path)
          {
              if (!Storage::disk('local')->exists($file_path)) {
                  abort(404);
              }
      
              $local_path = config('filesystems.disks.local.root') . DIRECTORY_SEPARATOR . $file_path;
      
              return response()->file($local_path);
          }
      }
      
    3. The last thing is register the route inside your routes/web.php file:

      Route::get('/storage/{file_name}', 'FileController')->where(['file_name' => '.*'])
      

    So there you have it, a pretty reusable snippet for all your projects that deals with private files :)

    0 讨论(0)
  • 2020-12-14 11:14

    It's really up to you. It'll need to be outside the public directory - I'd personally pick resources/uploads or storage/uploads, or store them off-server entirely using the cloud filesystem support.

    Whatever you pick, you'll need a route that fetches the file and passes it along to the user after first checking that they have access.

    0 讨论(0)
提交回复
热议问题