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();
}
}
}
我在 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();
}
}
}