Symfony - 将文件 url 重定向到控制器

Symfony - Redirect file url to controller

我在 Symfony 上创建了一个博客,并且实现了一个上传功能来将文件附加到帖子中。 我想拒绝直接访问文件(像这样:http://mysite/456/uploads/test.pdf)所以我制作了一个控制器来捕获 url

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class checkAccessAttachmentController extends AbstractController
{
    /**
     * @Route("/uploads/{post}/{file}", requirements={"file"="[0-9a-zA-Z_.]*"})
     */
    public function index(Post $post, string $file): Response
    {
        //TODO Check role / Blog post data / whatever
        //If ok : access file ok
        dd($file);
    }
}


URL 这样就可以了:https://mysite/uploads/14543/documentpdf

但是,当 URL 有一个点(通常在文件名中)时,它不起作用 这样的事情不起作用:https://mysite/uploads/14543/document.pdf 文件会自动下载,控制器不会被触发。

我认为在路由参数中需要指定一些东西,但我不知道是什么。

最后的精度:我已经在 Apache 上安装了 Symfony,symfony/apache-pack。所以已经创建了一个 htaccess,这可能是问题的原因

# Use the front controller as index file. It serves as a fallback solution when
# every other rewrite/redirect fails (e.g. in an aliased environment without
# mod_rewrite). Additionally, this reduces the matching process for the
# start page (path "/") because otherwise Apache will apply the rewriting rules
# to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
DirectoryIndex index.php

# By default, Apache does not evaluate symbolic links if you did not enable this
# feature in your server configuration. Uncomment the following line if you
# install assets as symlinks or if you experience problems related to symlinks
# when compiling LESS/Sass/CoffeScript assets.
# Options +FollowSymlinks

# Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
# to the front controller "/index.php" but be rewritten to "/index.php/index".
<IfModule mod_negotiation.c>
    Options -MultiViews
</IfModule>

<IfModule mod_rewrite.c>
    RewriteEngine On

    # Determine the RewriteBase automatically and set it as environment variable.
    # If you are using Apache aliases to do mass virtual hosting or installed the
    # project in a subdirectory, the base path will be prepended to allow proper
    # resolution of the index.php file and to redirect to the correct URI. It will
    # work in environments without path prefix as well, providing a safe, one-size
    # fits all solution. But as you do not need it in this case, you can comment
    # the following 2 lines to eliminate the overhead.
    RewriteCond %{REQUEST_URI}::[=12=] ^(/.+)/(.*)::$
    RewriteRule .* - [E=BASE:%1]

    # Sets the HTTP_AUTHORIZATION header removed by Apache
    RewriteCond %{HTTP:Authorization} .+
    RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]

    # Redirect to URI without front controller to prevent duplicate content
    # (with and without `/index.php`). Only do this redirect on the initial
    # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
    # endless redirect loop (request -> rewrite to front controller ->
    # redirect -> request -> ...).
    # So in case you get a "too many redirects" error or you always get redirected
    # to the start page because your Apache does not expose the REDIRECT_STATUS
    # environment variable, you have 2 choices:
    # - disable this feature by commenting the following 2 lines or
    # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
    #   following RewriteCond (best solution)
    RewriteCond %{ENV:REDIRECT_STATUS} =""
    RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/ [R=301,L]

    # If the requested filename exists, simply serve it.
    # We only want to let Apache serve files and not directories.
    # Rewrite all other queries to the front controller.
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ %{ENV:BASE}/index.php [L]

</IfModule>

<IfModule !mod_rewrite.c>
    <IfModule mod_alias.c>
        # When mod_rewrite is not available, we instruct a temporary redirect of
        # the start page to the front controller explicitly so that the website
        # and the generated links can still be used.
        RedirectMatch 307 ^/$ /index.php/
        # RedirectTemp cannot be used instead
    </IfModule>
</IfModule>

感谢您的帮助

htaccess 文件中有一部分,在 <IfModule mod_rewrite.c> 的末尾,上面写着:

# If the requested filename exists, simply serve it.
# We only want to let Apache serve files and not directories.
# Rewrite all other queries to the front controller.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ %{ENV:BASE}/index.php [L]

您可以注释掉或删除该行,但它也会影响所有其他文件(png、jpg、css、js 等...),或者您可以执行错综复杂的 RewriteCond仅影响特定目录中的 pdf,并希望该规则在服务器迁移后仍然存在。

或者您可以采用简单的方法。

简单的方法

  • 修改你的上传控制器,使文件保存在public目录之外的路径,甚至是另一个存储介质。
  • 将文件的元数据(路径、文件名、mimetype 等)保存在链接到 post 的 BD 中。另外,用 uniqid、md5、uuid 等生成一个 id 或 hash,并将其保存在元数据中以避免目录中的冲突 and/or 进一步混淆文件。
  • 在您的下载控制器中,询问您之前创建的文件 ID,并使用 BinaryFileResponse class 代替。

最后一点是这样的:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;

class checkAccessAttachmentController extends AbstractController
{
    /**
     * @Route("/uploads/{post}/{file_hash}", requirements={"file"="[0-9a-zA-Z_.]*"})
     * @Entity("file", expr="repository.findByHash(file_hash)")
     */
    public function index(Post $post, FileEntity $file): BinaryFileResponse
    {
        //TODO Check if $file belongs to post / role / Blog post data / whatever
        //The @Entity in the annotation handles the 404 if the id doesn't exists
        if ($check){
            $response = new BinaryFileResponse($file->getFullPath());
            $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $file->getFilename()); //DISPOSITION_ATTACHMENT forces download, even if browser can open the pdf. Use DISPOSITION_INLINE to let the browser decide
            return $response;
        } else {
            throw new AccessDeniedHttpException();
        }
    }
}