以编程方式添加来自 Laravel 包的 CSRF 检查的异常

2024-03-01

问题简而言之

我正在寻找一种方法来删除VerifyCsrfToken来自包内的全局中间件管道without用户必须修改App\Http\Middleware\VerifyCsrfToken。这可能吗?

用例

我正在开发一个包,可以轻松地将推送部署功能安全地添加到任何 Laravel 项目中。我从 Github 开始。Github 使用 webhook https://developer.github.com/webhooks/通知第三方应用程序有关事件的信息,例如推送或发布。换句话说,我会注册一个类似的 URLhttp://myapp.com/deploy http://myapp.com/deploy在 Github 上,Github 会发送一个POST向该 URL 发出请求,其中包含事件发生时的详细信息,我可以使用该事件来触发新的部署。显然,我不想在 Github 服务之外的某些随机(或可能是恶意)代理访问该 URL 的情况下触发部署。因此,Github 有保护 Webhooks 的流程 https://developer.github.com/webhooks/securing/。这涉及到在 Github 上注册一个密钥,他们将使用该密钥发送一个特殊的、安全散列的标头以及您可以用来验证它的请求。

我确保安全的方法包括:

随机唯一 URL/路由和密钥

首先,我自动生成两个随机的、唯一的字符串,它们存储在.env文件并用于在我的应用程序中创建密钥路由。在里面.env文件看起来像这样:

AUTODEPLOY_SECRET=BHBfCiC0bjIDCAGH2I54JACwKNrC2dqn
AUTODEPLOY_ROUTE=UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx

The config为此包创建两个密钥,auto-deploy.secret and auto-deploy.route我可以在注册路线时访问它,这样它就不会在任何存储库中发布:

Route::post(config('auto-deploy.route'),'MyController@index');

然后我可以访问 Github 并注册我的网络书,如下所示:

这样,部署 URL 和用于验证请求的密钥都将保持机密,并防止恶意代理在站点上触发随机部署。

用于验证 Webhook 请求的全局中间件

该方法的下一部分涉及为 Laravel 应用程序创建一个全局中间件,用于捕获和验证 Webhook 请求。我能够通过使用确保我的中间件在队列开头附近执行Laracasts 讨论线程中演示的方法 https://laracasts.com/discuss/channels/general-discussion/register-middleware-via-service-provider/?page=3。在里面ServiceProvider对于我的包,我可以在前面添加一个新的全局中间件类,如下所示:

public function boot(Illuminate\Contracts\Http\Kernel $kernel)
{
    // register the middleware
    $kernel->prependMiddleware(Middleware\VerifyWebhookRequest::class);
    // load my route
    include __DIR__.'/routes.php';
}

My Route好像:

Route::post(
    config('auto-deploy.route'), [
        'as' => 'autodeployroute',
        'uses' => 'MyPackage\AutoDeploy\Controllers\DeployController@index',
    ]
);

然后我的中间件会实现一个handle()方法看起来像这样:

public function handle($request, Closure $next)
{
    if ($request->path() === config('auto-deploy.route')) {
        if ($request->secure()) {
            // handle authenticating webhook request
            if (/* webhook request is authentic */) {
                // continue on to controller
                return $next($request);
            } else {
                // abort if not authenticated
                abort(403);
            }
        } else {
            // request NOT submitted via HTTPS
            abort(403);
        }
    }
    // Passthrough if it's not our secret route
    return $next($request);
}

此功能一直有效,直到continue on to controller bit.

问题的细节

当然,这里的问题是,由于这是一个POST请求,并且没有session()并且没有办法得到CSRF提前代币,全球VerifyCsrfToken中间件生成一个TokenMismatchException并中止。我已经阅读了大量的论坛帖子,并深入研究了源代码,但我找不到任何干净、简单的方法来禁用VerifyCsrfToken用于这一请求的中间件。我尝试了几种解决方法,但由于各种原因我不喜欢它们。

解决方法尝试#1:让用户修改VerifyCsrfToken中间件

解决此问题的记录和支持的方法是将 URL 添加到$except数组中的App\Http\Middleware\VerifyCsrfToken类,例如

// The URIs that should be excluded from CSRF verification
protected $except = [
    'UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx',
];

显然,这样做的问题是,当这段代码被签入存储库时,任何碰巧查看的人都会看到它。为了解决这个问题,我尝试过:

protected $except = [
    config('auto-deploy.route'),
];

但 PHP 不喜欢它。我还尝试在这里使用路线名称:

protected $except = [
    'autodeployroute',
];

但这也行不通。它必须是实际的 URL。其实那件事确实有效是重写构造函数:

protected $except = [];

public function __construct(\Illuminate\Contracts\Encryption\Encrypter $encrypter)
{
    parent::__construct($encrypter);
    $this->except[] = config('auto-deploy.route');
}

但这必须是安装说明的一部分,并且对于 Laravel 包来说是一个不寻常的安装步骤。我有一种感觉,这就是我最终会采用的解决方案,因为我想要求用户这样做并不是那么困难。它的好处是至少可能让他们意识到他们将要安装的软件包会规避 Laravel 的一些内置安全性。

解决方法尝试#2:catch the TokenMismatchException

我尝试的下一件事是看看我是否可以捕获异常,然后忽略它并继续,即:

public function handle($request, Closure $next)
{
    if ($request->secure() && $request->path() === config('auto-deploy.route')) {
        if ($request->secure()) {
            // handle authenticating webhook request
            if (/* webhook request is authentic */) {
                // try to continue on to controller
                try {
                    // this will eventually trigger the CSRF verification
                    $response = $next($request);
                } catch (TokenMismatchException $e) {
                    // but, maybe we can just ignore it and move on...
                    return $response;
                }
            } else {
                // abort if not authenticated
                abort(403);
            }
        } else {
            // request NOT submitted via HTTPS
            abort(403);
        }
    }
    // Passthrough if it's not our secret route
    return $next($request);
}

是的,现在就来嘲笑我吧。傻兔,不是这样的try/catch作品!当然$response内未定义catch堵塞。如果我尝试做$next($request) in the catch块,它只是撞击TokenMismatchException again.

解决方法尝试#3:在中间件中运行我的所有代码

当然,我可以忘记使用Controller用于部署逻辑并触发中间件的所有内容handle()方法。请求生命周期将在那里结束,我永远不会让中间件的其余部分传播。我不禁觉得这有些不优雅,而且它偏离了 Laravel 构建的整体设计模式,以至于最终导致维护和协作变得困难。至少我知道它会起作用。

解决方法尝试#4:修改Pipeline

Philip Brown 有一篇很棒的教程,描述了管道模式 http://culttt.com/2015/09/28/how-to-use-the-pipeline-design-pattern-in-laravel/以及它是如何在 Laravel 中实现的。 Laravel 的中间件使用了这种模式。我想也许,只是也许,有一种方法可以访问Pipeline对象对中间件包进行排队,循环遍历它们,并删除我的路由的 CSRF 包。据我所知,有办法add管道中添加了新元素,但无法找出其中的内容或以任何方式对其进行修改。如果您知道方法,请告诉我!

解决方法尝试#5:使用WithoutMiddleware trait

我还没有彻底研究过这个问题,但似乎最近添加了这一特性,以便允许测试路由而不必担心中间件。它显然不适合生产,禁用中间件意味着我必须想出一个全新的解决方案来弄清楚如何让我的包完成它的任务。我决定这不是我该走的路。

解决方法尝试#6:放弃。只需使用Forge https://forge.laravel.com/ or Envoyer https://envoyer.io/

为什么要重新发明轮子?为什么不直接为这些已经支持推送部署的服务中的一项或两项付费,而不是麻烦地滚动我自己的软件包呢?嗯,首先,我每月只为我的服务器支付 5 美元,因此每月为其中一项服务另外支付 5 美元或 10 美元在经济上感觉不太合适。我是一名老师,他开发应用程序来支持我的教学。它们都不产生收入,虽然我可能负担得起,但随着时间的推移,这种事情会增加。

讨论

好吧,我花了两天的大部分时间来解决这个问题,这就是我来这里寻求帮助的原因。你有解决方案吗?如果您已经读到这里,也许您会沉迷于一些结束语。

想法#1:为 Laravel 人员认真对待安全性而喝彩!

编写一个绕过内置安全机制的包是多么困难,这给我留下了深刻的印象。我并不是以“我试图做坏事”的方式谈论“规避”,而是从某种意义上说,我正在尝试编写一个合法的软件包,以节省我和许多其他人的时间,但实际上会要求他们“相信我”其应用程序的安全性,因为这可能会导致恶意部署触发器。这应该很难做到正确,而且确实如此。

想法#2:也许我不应该正在做这个

通常,如果某些事情很难或不可能在代码中实现,那就是设计使然。也许我想自动化这个包的整个安装过程是因为 Bad Design™。也许这就是代码告诉我,“不要这样做!”你怎么认为?

总结来说,这里有两个问题:

  1. 你知道一种我没有想到的方法吗?
  2. 这是糟糕的设计吗?我不应该这样做吗?

感谢您的阅读,也感谢您的深思熟虑的回答。

附:在有人说之前我就知道这可能是重复的 https://stackoverflow.com/questions/33177674/is-there-a-way-to-exclude-a-route-from-csrf-protection-from-within-a-package-in,但我提供了比另一位发帖者更多的细节,他也从未找到解决方案。


我知道在生产代码中使用 Reflection API 不是一个好习惯,但这是我能想到的唯一不需要额外配置的解决方案。这更像是一个概念证明,我不会在生产代码中使用它。

我认为更好、更稳定的解决方案是让用户更新他的中间件以使用您的包。

tl;dr - 您可以将其放入软件包引导代码中:

// Just remove CSRF middleware when we hit the deploy route
if(request()->is(config('auto-deploy.route')))
{
    // Create a reflection object of the app instance
    $appReflector = new ReflectionObject(app());

    // When dumping the App instance, it turns out that the
    // global middleware is registered at:
    // Application
    //  -> instances
    //   -> Illuminate\Contracts\Http\Kernel
    //    -> ... Somewhere in the 'middleware' array
    //
    // The 'instance' property of the App object is not accessible
    // by default, so we have to make it accessible in order to
    // get and set its value.
    $instancesProperty = $appReflector->getProperty('instances');
    $instancesProperty->setAccessible(true);
    $instances = $instancesProperty->getValue(app());
    $kernel = $instances['Illuminate\Contracts\Http\Kernel'];

    // Now we got the Kernel instance.
    // Again, we have to set the accessibility of the instance.
    $kernelReflector = new ReflectionObject($kernel);
    $middlewareProperty = $kernelReflector->getProperty('middleware');
    $middlewareProperty->setAccessible(true);
    $middlewareArray = $middlewareProperty->getValue($kernel);

    // The $middlewareArray contains all global middleware.
    // We search for the CSRF entry and remove it if it exists.
    foreach ($middlewareArray as $i => $middleware)
    {
        if ($middleware == 'App\Http\Middleware\VerifyCsrfToken')
        {
            unset($middlewareArray[ $i ]);
            break;
        }
    }

    // The last thing we have to do is to update the altered
    // middleware array on the Kernel instance.
    $middlewareProperty->setValue($kernel, $middlewareArray);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以编程方式添加来自 Laravel 包的 CSRF 检查的异常 的相关文章

  • 使用 md5 加密的 PHP 和 Mysql 查询出现问题

    我使用普通的 php mysql 插入查询并使用 md5 加密密码 这是插入查询 sql mysql query INSERT INTO user username password role approved values usernam
  • 从 FilterControllerEvent 监听器重定向到另一个 Symfony 路由

    我正在尝试设置一个 kernel controller 侦听器 以便在函数返回 true 时重定向到另一个路由 我有可用的路线 但无法使用此路线设置控制器 event gt setController 我收到以下错误 FilterContr
  • 在 PHP 中将 CSV 写入不带括号的文件

    是否有本机函数或实体类 库用于将数组写入 CSV 文件中的一行而无需封装 fputcsv将默认为 如果没有为封装参数传入任何内容 谷歌让我失望了 返回一大堆有关的页面的结果 fputcsv PEAR 的库做的事情或多或少与fputcsv 工
  • 关于加拿大短信网关提供商的建议[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我很好奇 如果我能够接受传入的短信到某个号码 然后将其传递给 PHP 中的服务器端应用程序 会带来多少麻烦 金钱 我最终会通过电子邮件地址发回短信 有
  • 如何在 Zend Framework 中存储 cron 作业的脚本?

    因为 ZF 的所有 URL 都依赖于 mod 重写 所以我并不清楚应该在哪里存储用于 cron 作业的本地脚本 有人有什么建议 或者有 正式接受 的方式吗 我用模块化目录结构 http framework zend com manual e
  • 覆盖供应商自动加载编辑器

    有没有办法让您创建的自动加载文件在调用供应商自动加载之前运行 我们似乎遇到了 SimpleSAML 的自动加载覆盖我们创建的自动加载文件之一的问题 我是 Composer 的新手 似乎无法在网上找到任何解决方案 我尝试将我们的自动加载文件包
  • 具有动态表单名称的 form_widget

    在我的 Twig 模板中 我有一个 FOR 循环 它创建多个表单 如下所示 for thing in things set form id myform thing Id set form name attribute form myfor
  • 使用 PHP 将值插入可编辑 PDF,并保持可编辑状态

    我有一个带有可编辑字段的 PDF 我希望将 HTML 表单中的值传递到此 PDF 中 我尝试过使用 FPDF 并且它有效 但是将值传递到 PDF 后 pdf 中的字段不再可编辑 另一个缺点是 在将值传递到 PDF 时 我们必须为每个字段指定
  • TOMCAT 6 中的 PHP - 异常

    我一直在努力融入PHP in APACHE TOMCAT 6依照指示second answer为了QUESTION https stackoverflow com questions 779246 run a php app using t
  • php - 我应该加密电子邮件地址吗?

    当用户注册时 我应该将他们的电子邮件按原样存储在数据库中还是对其进行哈希处理 我希望稍后能够解密 那么我应该使用 md5 吗 谢谢你 No md5 is 单向哈希函数 http en wikipedia org wiki Cryptogra
  • php 中的简单授权/登录功能

    我希望第一次实现用户登录到我的网站 我很高兴构建自己的解决方案 或者实现一些开源的东西 但是到目前为止 在我的搜索中没有任何包是明显的选择 同样 我完全意识到 作为一名中级 php 程序员 如果我推出自己的解决方案 并真正敞开大门 我很可能
  • AWS S3 上传的图像已损坏

    我正在 AWS ec2 ubuntu 机器上工作 我的代码在 cakephp 中 当我尝试将任何图像上传到 AWS S3 时 它都会损坏 虽然它在核心 php 代码中运行良好 这是我的控制器代码 if this gt User gt sav
  • 运行PHPUnit测试时如何避免内部调用函数?以及如何设置内部性能的模拟数据?

    我有一个类 Receipt php
  • 使用 DOJO 自动完成文本框

    我正在寻找一种使用 DOJO 进行文本框自动建议的简单方法 我将查询的数据库表 使用 PHP 脚本 以 JSON 形式返回 有超过 100 000 条记录 因此这确实不应该采用 FilteringSelect 或 ComboBox 的形式
  • 如何在 HTML / Javascript 页面中插入 PHP 下拉列表

    好吧 这是我的第二篇文章 请接受我是一个完全的新手 愿意学习 花了很多时间在各个网站上寻找答案 而且我几乎已经到达了我需要到达的地方 至少在这一点上 我有一个网页 其中有许多 javascript 函数 这些函数一起使用 google 地图
  • php 表单提交 - Q2

    我对这个虚拟问题感到抱歉 这是我的简单 PHP 表单 其中包含两个 SQL 表和 ADD 提交 按钮 我希望将人员从 Test1 转移到 Test2 很多事情都很好 只有提交按钮不起作用 因此 Test2 表没有反馈 Revised 现在提
  • 为什么 PHP 中不允许“传统”类型提示?

    刚刚发现类型提示 http php net manual en language oop5 typehinting phpPHP 中允许 但不适用于整数 字符串 布尔值或浮点数 为什么 PHP 不允许对整数 字符串等类型进行类型提示 从 P
  • 检查文件权限

    我怎样才能检查file permissions 无需通过运行操作系统特定命令passthru or exec Use 文件权限 http php net fileperms功能 clearstatcache echo substr spri
  • Netbeans 和 Git,.obj 文件被忽略

    我正在开发一个涉及 obj 文件的小型 git 项目 当我查看 项目选项卡 时 我发现它们被忽略了 但如果我查看我的 gitignore 我无法理解为什么 DepthPeeling nbproject private DepthPeelin
  • 在 PHP 中模拟 jQuery.ajax 请求

    我必须在 PHP 中模拟 AJAX 请求 就像在 jQuery 中一样 我当前的代码在这里 原始 AJAX 调用 不得修改 ajax type POST url someFile php data data success function

随机推荐

  • Android:检测软键盘打开

    当软键盘打开时 我想要一个滚动视图向下滚动到底部 为此我可以使用 fullScroll View FOCUS DOWN 但是 在软键盘打开事件触发后如何触发该命令 这是我的解决方案 1 简单的界面 public interface Keyb
  • 无法使用 MFMailComposeViewController 在应用程序中发送电子邮件

    我有一个应用程序使用MFMailComposeViewController通过电子邮件发送文档 我在某处读到我需要启用至少一封电子邮件 以便该方法 MFMailComposeViewController canSendEmail 将返回 Y
  • 在 BeautifulSoup 中处理无限滚动 UI

    我正在研究如何抓取 Linkedin 源代码 https www linkedin com mynetwork invite connect connections https www linkedin com mynetwork invi
  • 当“使用其他帐户”登录时,Google Sign in 不会返回任何结果

    这似乎是一个基本功能 但遵循登录教程 https developers google com identity sign in android sign in 仅当我选择设备上已注册的帐户时 它才有效 选择后 使用另一个帐户 并完成一些步骤
  • 使用 jQuery fadeIn 和 fadeOut 的 Google Maps API V3 InfoBox

    我在网上到处搜索 但找不到使用 jQuery 淡出 Google 地图中的 InfoBox InfoWindow 的教程或示例 而不是实际框 窗口的内容 这是我的代码 我不确定我做错了什么 但有些东西似乎也不正确 google maps e
  • 通过 Windows Scheduler 运行 python 脚本不起作用

    我正在尝试通过 Windows 任务计划程序自动执行 python 脚本 但它不起作用 在我的 python 脚本的末尾 应该创建两个 CSV 文件 但没有创建 我尝试了以下方法 1 将我的python exe的地址复制到Program S
  • 将 Tensorflow 数据集 API 创建的数据集拆分为训练和测试?

    有谁知道如何将 Tensorflow 中的数据集 API tf data Dataset 创建的数据集拆分为测试和训练 假设你有all dataset的变量tf data Dataset type test dataset all data
  • 使用缓存文件还是一个以上的 HTTP 请求?

    在所有 加速你的网站 网站和书籍上 他们总是告诉我们不惜一切代价尽量减少 HTTP 请求 这很好 但如果这意味着在每个页面上你都必须一次又一次地重新加载 120kb 因为用户缓存是空的怎么办 如果我在网站的每个页面上使用 5 个 js 文件
  • Chrome 调试器 - 如何关闭 console.log 消息分组?

    比如说 在我的 Google Chrome 扩展中我这样做 console log msg Chrome 调试器将类似的消息分组如下 有没有什么办法可以将其关闭并让消息按原样发布 它只会折叠相同的连续行 我不认为这有什么问题 但是通过控制台
  • Powershell 5 中的哪些变化改变了块大括号的含义

    我们最近将构建服务器上的 Powershell 版本从 4 0 更新到了 5 0 这一更改导致我们的一个构建脚本开始以意外的方式失败 该代码用于确定我们的产品中应包含哪些用户指南 该代码处理一个 xml 节点列表 这些节点描述了所有可用文档
  • Java 中包私有类的改进

    根据我的经验 Java 中类的包私有可见性被证明是多余的 包私有可见性似乎基于这样一个前提 几乎被另一个类私有使用的类很可能保存在同一个包中 通常情况并非如此 有人正在探索改进的访问修饰符 替代机制吗 尝试使用包私有可见性时出现问题 我们很
  • 在 apache 模块中转换 PHP 类

    我在 php5 中编写了一个复杂的系统 许多类使用静态方法并包含其他文件 现在我想分发 ant 我会选择模块方式 所以我喜欢创建一个像 myFramework so 这样的模块并将其包含在 Apache 中 也许 之后 当我编写一些 php
  • Angular AOT 和 Rollup - 未捕获的 ReferenceError:未定义导出

    我正在尝试实现 Angular 的 AOT 教程 https angular io docs ts latest cookbook aot compiler html https angular io docs ts latest cook
  • 比较匹配器在混合数字类型上失败

    在普通 Scala 中 以下断言通过 assert 1D gt 0F assert 1F gt 0 assert 1L gt 0 assert 1 gt 0 toShort assert 1 toShort gt 0 toChar 然而 S
  • XSLT 连接字符串,删除最后一个逗号

    我需要使用 XSLT 构建一个字符串 并用逗号分隔每个字符串 但在最后一个字符串后面不包含逗号 在下面的示例中 如果我有 分发 节点而不是 注释 节点 那么我将有一个尾随逗号 我不知道如何构建一个字符串作为变量 然后截断 XSLT 中的最后
  • 让php将句子分解为单词

    我可能误解了文档 但是当我编码时 explode here s a sentence 2 我最终 Array 0 gt here s 1 gt a sentence with a few words in it 有没有办法让爆炸回来 Arr
  • 为什么这段 PHP 代码只回显“Array”?

    这是我的代码 if isset POST check AND POST check First errormessage array if empty POST full name strlen POST full name lt 4 er
  • Windows服务“自托管”WCF:压缩?

    在 IIS 中使用 WCF 压缩我可以找到文档 但它面向使用 IIS 功能 我可以找到人们谈论他们如何编写自己的压缩处理程序 但这一切看起来都很定制 是否有压缩 WCF 的最佳实践 我们正在使用 http 绑定 编辑 将其设置为维基 没有任
  • Dart 中的字符串文字类型类似物

    我知道在 Dart 中没有字符串文字类型 例如 interface IButtonProps readonly variant primary secondary readonly size small regular big a litt
  • 以编程方式添加来自 Laravel 包的 CSRF 检查的异常

    问题简而言之 我正在寻找一种方法来删除VerifyCsrfToken来自包内的全局中间件管道without用户必须修改App Http Middleware VerifyCsrfToken 这可能吗 用例 我正在开发一个包 可以轻松地将推送