如何限制对任何路由的直接访问并抛出 404 异常,即使存在路由?

Posted

技术标签:

【中文标题】如何限制对任何路由的直接访问并抛出 404 异常,即使存在路由?【英文标题】:How do i restrict direct access of any route and throw 404 exception even routes present? 【发布时间】:2020-01-01 17:01:10 【问题描述】:

我想添加对从地址栏直接访问任何 URL 的限制。如果用户在地址栏中输入任何 URL,那么即使 URL 存在,它也应该抛出 404 异常。任何人都可以有一个想法,请提出建议。 提前致谢。

【问题讨论】:

真正的问题是为什么您要阻止这种情况。因为它不会通过阻止直接输入 url 来使网站受到更多保护。也会阻止喜欢这种方式工作的用户使用这个功能。 OP,您能否解释一下为什么您需要防止这种情况发生,因为我们可能会找到更好的解决方案:) 【参考方案1】:

只需将 /test 替换为您的实际路线:

Route::get('/test', function()
   return abort(404);
);

【讨论】:

【参考方案2】:

根据您的问题和 cmets(请参阅:“这仅适用于单页解决方案,我的问题是假设我现在有 user/1 页,任何用户更改 URL 中的 id,它应该重定向到 404 页。没有直接的 URL 操作允许这就是我想要的。”),看来这是您要解决的问题:

用户 x 想要查看他们的个人资料。此配置文件的路由是 mysite.local/user/x。 'x' 代表用户的 id。如果用户 x 输入 mysite.local/user/y,他们应该看不到用户 y 的个人资料,他们应该收到 403(禁止)。

我建议使用 HTTP 状态码 403,因为该页面在技术上存在,只是不允许用户查看它。

您可以通过多种方式进行此操作 - 您不必专注于 URL 被“输入”,因为您可以通过多种可能更安全的方式解决此问题。

假设您使用 Laravel Auth,您可以在相关页面的控制器中添加验证检查。因此,使用您在上面的 cmets 中提供的场景,其中您有一个 'user/1' 的路由,其中​​链接的控制器函数接受参数 User $user,您可以执行以下操作:

if ($user->id !== Auth::id()) 
    return abort(403, 'Forbidden'); // you can throw this as a 404 if you like

您可以将其保存为函数,即private function checkForAuth(User $user),并在控制器中每个函数的开头调用它。

现在,这可能会改变 - 假设我是管理员,我应该仍然可以去查看 mysite.local/user/x,因为管理员有权查看 - 太棒了,我们可以为那也是。

例如,在您的用户模型中,您可以像这样设置一个函数:

public function canBeViewedBy(User $user) : bool


     $adminAccounts = ... // however you decide as user is an 'admin', pluck IDs

     // If you are the user, you can view your own profile
    if ($user->id == $this->user_id) 
        return true;
    

    // If you are an admin, you can view this user
    if (in_array($this->user_id, $adminAccounts)) 
        return true;
    

    return false;


然后您可以改为在控制器函数的开头调用它,如下所示:

if ($user->canBeViewedBy(Auth::user()) ....

如果您的管理员或用户具有不同级别的查看权限,这就是中间件的用武之地。我将为角色创建一个模型和表组合,并在您的用户模型中包含:

public function roles()

    return $this->belongsToMany('App\Role');


public function hasRole(Role $role)

    $roles = $this->roles()->where('name', $role->name)->first();

    if ($roles) 
        return true;
    

    return false;


然后您可以在某些中间件中调用它,例如Role.php 使用 Role $role 实例化:

    // redirects any requests which have no logged in user
    if (is_null(Auth::id())) 
        return Redirect::route('login');
    

    if (!$user->hasRole($role)) 
        return abort(403, 'Forbidden');
    

    // otherwise process the request, all good - process the next request!

如果你想用你的角色中间件保护整个控制器,你可以在你的控制器构造函数中做到这一点:

public function __construct()

    $this->middleware('role:administrator');

或者在 IGP 解释的路由中。

这将锁定您的页面,以确保只有正确的人可以访问这些页面,没有 GDPR 火灾警报,一切都很好!这比说“这个人自己输入了路线”要安全得多,因为你说的是​​“这个人有访问这个页面的正确权限”。

抱歉,有点长,如果您有任何反馈意见,请告诉我。

【讨论】:

【参考方案3】:

我能想到 3 个解决方案,每个都有自己的缺陷:

客户端路由

https://medium.com/@fro_g/routing-in-javascript-d552ff4d2921

这样,直接页面不必存在于服务器上。 Javascript 将处理历史状态。

检查推荐人

$_SERVER['HTTP_REFERER']

这将显示用户来自的上一个页面(根据浏览器)。直接访问 url 时为空。

在首页后专门使用 POST 页面

由于浏览器能够在提交表单等时显示 POST 页面。它只能通过输入 url 来显示 GET 请求。

在所有情况下,用户都可以欺骗服务器,就好像他以预期的方式导航到页面一样。 这意味着没有 100% 确定的方法可以禁用直接访问,因为您无法信任客户端。

【讨论】:

【参考方案4】:

只需使用middleware

如果您不想定义自己的middleware,请使用Gate

# A service provider. Can be app\Providers\AuthServiceProvider.php for example
public function boot()

    $this->registerPolicies();
    // Only users 1 through 6 can view-pages. Write your own logic.
    Gate::define('view-pages', function ($user) 
        $allowed = [1,2,3,4,5,6]
        return in_array($user->id, $allowed);
    );

如果它变得太混乱并且您想使用不同的文件,请阅读政策。这与在路由文件中使用Controllers 而不是使用Closures 基本相同。

然后您可以在路由上使用can 中间件。

https://laravel.com/docs/5.8/routing#route-group-middleware

# routes/web.php
// can:view-pages checks if User passes the defined Gate view-pages.
// If it doesn't, throws a 403 (Forbidden).
Route::middleware('can:view-pages')->group(function()
    Route::get(...)
    Route::post(...)
    ...
);
// Can also be done route by route
Route::get('path/to/', 'SomeController@someAction')->middleware('can:view-pages');

如上所述,这会引发 403。这是因为中间件中的 Gate 检查失败(或在 Controller 中使用 $this->authorize())会抛出 Illuminate\Auth\Access\AuthorizationException,Laravel 通过抛出 403 来处理。

如果您想自定义此行为,可以使用Exception handler 的render 方法来实现。

# app\Exceptions\Handler.php
public function render($request, Exception $exception)

    if ($exception instanceof \Illuminate\Auth\Access\AuthorizationException) 
        abort(404);
    

    return parent::render($request, $exception);

【讨论】:

这仅适用于单页解决方案我的问题是假设我有 user/1 页面现在任何用户更改 URL 中的 id 它应该重定向到 404 页面。不允许直接的 URL 操作,这就是我想要的。 OP 没有询问权限/角色。如果用户输入 url,他希望阻止页面出现,但如果他将他重定向到那里,则阻止页面出现。 听起来他想自己制作中间件。 但是使用中间件你如何识别这个请求来自重定向而不是直接来自地址栏? 请求对象可能就足够了。它会给你一些信息,比如推荐人。例如标题和引荐来源网址。中间件只是为了避免重复太多代码。【参考方案5】:

服务器端,你不能。

反正不容易。对于服务器来说,一个请求就是一个请求,实际上并没有请求标头标志来告诉您用户是否手动输入了URL

你可以做的一件事,手动输入URL只能做GET请求。

一个想法是跟踪用户在您的站点/api 中的导航,并列出可能的转换。

【讨论】:

如果我在成功创建订单后有“成功”页面怎么办。此页面应仅在订单后显示,但用户从地址栏中输入直接 url 以访问它而不处理订单? @sandy 这需要一个业务解决方案,而不是一个编码解决方案。例如,您可以检查用户刚刚提交的订单以向他显示成功页面,或者与成功页面相关的订单是否存在。或者在某处(cookies、数据库、缓存)注册该订单的成功页面是否已经显示......很多解决方案

以上是关于如何限制对任何路由的直接访问并抛出 404 异常,即使存在路由?的主要内容,如果未能解决你的问题,请参考以下文章

laravel 访问不存在的路由跳转问题!(异常处理)

C++ Int 似乎没有初始化并抛出异常:读取访问冲突

为啥spring websocket应用看不到html并抛出404错误?

如何解决'定义并抛出专用异常而不是使用通用异常'。

Apache Beam - 即使程序连续执行,也会捕获并抛出异常。如何停止该进程或在管道中处理

异常处理——捕获并抛出