首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
185 阅读
2
php接口优化 使用curl_multi_init批量请求
145 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
34
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
560
篇与
的结果
2023-08-10
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)尽管大多数 PHP 开发人员都知道如何使用 Composer,但并不是所有的人都在有效的或以最好的方式来使用它。 所以我决定总结一些在我日常工作流程很重要的东西。大多数技巧的哲学是 “稳,不冒险”,这意味着如果有更多的方法来处理某些事情,我会使用最有把握不容易出错的方法。Tip #1: 阅读文档我是真心这样认为的. 文档 是一个非常有用的东西并且在长远看来几个小时的阅读将会为您节省更多的时间。你会惊讶原来 Composer 可以做这么多事情.Tip #2: 注意 项目 和 库 之间的区别无论你是创建 项目 还是 库 , 了解这一点非常重要,它们每一个都需要单独的一套做法。一个库是一个可重用的包,你可以添加一个依赖比如: symfony / symfony,doctrine / orm 或者 elasticsearch/elasticsearch.一个项目通常是一个应用程序,依赖于几个库。它通常是不可重用的(没有其它项目会要求它作为依赖。典型的例子是电子商务网站,客户支持系统等)。我将在下面的提示中区分库和项目。Tip #3: 使用特定的依赖关系 #关于应用程序的版本如果你正在创建一个应用程序,您应该使用最具体的版本来定义依赖项,假如你需要解析 YAML 文件,你应该指定这样的依赖版本 “symfony / YAML”:“4.0.2”。即使你遵循了库的依赖版本控制,在次要版本和补丁版本中也有可能中断向后兼容性。例如,如果你使用的是 “symfony / Symfony”:“^ 3.1”, 会有 3.2 的版本有可能会破坏你的应用程序测试用列。或者可能新版本有 bug 没有修正,那么 php_codesniffer 检测你的代码格式的时候会导致新问题,这在很大程度上可能会破坏一个应用的构建。依赖关系的更新应该是深思熟虑的,而不是偶然的。其中的一个技巧更详细地讨论了它。这听起来像一个多余的,但它可以防止你的同事不小心在项目中添加一个新的库文件或更新所有依赖的时候出错,(不然的话,你们有可能会导致浪费大量的时间在审核代码上)。Tip #4: 对库依赖项使用版本范围创建库时,应尽可能定义最大的可用版本范围。比如创建了一个库,要使用 symfony/yaml 库进行 YAML 解析,就应这样写:"symfony/yaml": "^3.0 || ^4.0"这表示该库能从 Symfony 3.x 或 4.x 中任意版本中使用 symfony/yaml 。这相当重要,因为这个版本约束会传递给使用该库的应用程序。万一有两个库的请求存在冲突,比如一个要 ~3.1.0 ,另一个需要 ~3.2.0 ,则安装会失败。Tip #5: 开发应用程序要提交 composer.lock 文件到 git 版本库中创建了 一个项目,一定要把 composer.lock 文件提交到 git 中。 这会确保每一个人 —— 你、你的合作伙伴、你的 CI 服务器以及你的产品服务器 —— 所运行的应用程序拥有相同依赖的版本。乍一看有些画蛇添足,在 Tip #3 中已经提过要使用明确的版本号的约束了啊。这并不多余,要知道你使用的依赖项的依赖项并不受这些约束绑定(如 symfony/console 还依赖 symfony/polyfill-mbstring)。如果不提交 composer.lock 文件,就不会获取到相同版本的依赖集合。Tip #6: 开发库要把 composer.lock 文件添加到 .gitignore 文件中创建 一个库 (比如说叫 acme/my-library), 这就不应该把 composer.lock 文件提交到 git 库中了。该文件对使用该库的项目 It 不会有任何影响 。假设 acme/my-library 使用 monolog/monolog 作依赖项。你已经在版本库中提交了 composer.lock,开发 acme/my-library 的每个人都可能在使用 Monolog 的老旧版本。该库开发完成后,在实际项目中使用该库,就可能存在安装的 Monolog 是一个新版本 , 而此时就会和该库存在不兼容。可是你在之前根本就不会注意到兼容问题就因为这个 composer.lock!因此,最佳处理方式就是把 composer.lock 添加到 .gitignore 文件中,这样就避免了不小心提交它到版本库中引发的问题。如果还想确保该库与它的依赖项的不同版本保持兼容性,那继续阅读下一个 Tip !Tip #7: Travis CI 构建依赖项的不同版本当前 Tip 仅适合库(对于应用程序要指明具体的版本号)。如果你在构建开源的库,很有可能你会使用 Travis CI 来跑构建过程。默认情况下,在 composer.json 文件约束允许的条件下,composer 安装会安装依赖的最新可能版本。这就意味着对于 ^3.0 || ^4.0 这样的依赖约束,构建安装总是使用最新的 v4 版本发行包。 而 3.0 版本根本不会测试,所构建的库就可能与该版本不兼容,你的用户要哭了。幸好,composer 为安装低版本依赖项提供了一个开关 --prefer-lowest (应使用 --prefer-stable ,可阻止不稳定版本的安装)。已上传的 .travis.yml 配置类似下面的格式:language: phpphp:7.17.2env: matrix:- PREFER_LOWEST="--prefer-lowest --prefer-stable" - PREFER_LOWEST="" before_script:composer update $PREFER_LOWESTscript:composer ci代码详见 my mhujer/fio-api-php library 及 the build matrix on Travis CI虽然这解决了多数的不兼容问题,不过仍然要记得,依赖项的最低和最高版本间有太多的组合。他们仍旧可能存在不兼容的情况。Tip #8: 按名称对 require 和 require-dev 中的包排序按名称对 require 及 require-dev 中的包排序是非常好的实践。这在衍合一个分支时可以避免不必要的合并冲突。假如你把一个包添加到两个分支文件中的列表末尾,那每次合并都可能遇到冲突。手动进行包排序的话会很乏味,所以最好办法就是在 composer.json 中 配置一下 即可:{..."config": { "sort-packages": true },...}以后再要 require 一个新的包,它会自动添加到一个正确位置(不会跑到尾部)。Tip #9: 进行版本衍合或合并时不要合并 composer.lock 如果你在 composer.json (和 composer.lock)中添加了一个新依赖项,并且在该分支被合并前主分支中添加另一个依赖项,此时就需要对你的分支进行衍合处理。那么 composer.lock 文件就会得到一个合并冲突。千万别试图手动解决冲突,这是因为 composer.lock 文件包含了定义 composer.json 中依赖项的哈希值。所以即使你解决了冲突,这个最终合并结果的 lock 文件仍是错误的。最佳方案应该这样做,用下面一行代码在项目根目录创建一个 .gitattributes 文件,它会告诉 git 不要试图对 composer.lock 文件进行合并操作:/composer.lock -merge推荐 Trunk Based Development 方式(常用佳品,不会有错),使用临时的特性分支纠正这种问题。当你有个临时分支需要即时合并时,因此导致的 composer.lock 文件合并冲突的风险极小。你甚至可以仅仅为添加一个依赖项而创建分支,然后马上进行合并。假如在衍合过程中 composer.lock 遇到合并冲突又当如何呢? 使用主分支版本解决,这样仅仅修改 composer.json 文件即可(新增一个包)。然后运行 composer update --lock ,就会把 composer.json 文件的修改更新到 composer.lock 文件中。现在把已经更新的 composer.lock 文件提交到版本暂存区,然后继续衍合操作。Tip #10: 了解 require 和 require-dev 之间的区别能够意识到 require 和 require-dev 模块之间的区别是非常重要的。需要运行在应用中或者库中的包都应该被定义在 require (例如: Symfony, Doctrine, Twig, Guzzle, ...) 中。如果你正在创建一个库, 注意将什么内容定义为 require。因为这个部分的 每个依赖项同时也是使用了该库的应用的依赖。 开发应用程序 (或库) 所需的包应该定义在 require-dev (例如:PHPUnit, PHP_CodeSniffer, PHPStan) 中。Tip #11: 安全地升级依赖项我想大家对如下事实存有共识:应该定期对依赖项升级。 此处我想讨论的是依赖项的升级应该放在明处且慎之又慎,而不能是因其他活计的需要才顺手为之。如果在重构应用的同时又升级了库,那么就很难区分应用崩溃的原因是重构还是升级带来的。可用 composer outdated 命令查看哪些依赖项需要升级。追加一个 --direct (或 -D)参数开关是个聪明之举,这只会查看 composer.json 指定的依赖项。还有一个 -m 参数开关,只查看次版本号的升级列表。对每一个老版本的依赖项进行升级都要尊循如下步骤:创建新分支在 composer.json 文件中更新该依赖项版本到最新版本号运行 composer update phpunit/phpunit --with-dependencies (使用升级过的库替换 phpunit/phpunit)检查 Github 上库的版本库中 CHANGELOG 文件,检查是否存在重大变化。 如果存在就升级应用程序本地测试应用程序(使用 Symfony 的话还能在调试栏看到弃用警告)提交修改(包括 composer.json 、 composer.lock 及其他新版本正常运行所做的必要修改)等 CI 构建结束合并然后部署有时需要一次升级多个依赖项,比如升级 Doctrine 或 Symfony。这种情况下,就要在升级命令中把他们全部罗列出来:composer update symfony/symfony symfony/monolog-bundle --with-dependencies或者使用通配符升级所有指定命名空间的依赖:composer update symfony/* --with-dependencies这全都是很乏味的工作,但相对于不小心升级依赖项而言,这提供了额外保障。一个可接受的简捷方式就是一次升级所有 require-dev 中的依赖项(如果程序代码没有修改的话,否则还是建议创建独立分支以便代码审查)。Tip #12: 在 composer.json 中定义其他类型的依赖除了定义库作为依赖项外,也以在这儿定义其他东西。可以定义应用程序和库所支持的 PHP 版本:"require": {"php": "7.1.* || 7.2.*",},也能定义应用程序和库所需要的扩展。在尝试 docker 化自己的应用时,或是你的同伴头一次设置应用环境时,这招超级实用。"require": {"ext-mbstring": "*", "ext-pdo_mysql": "*",},(当 扩展版本不一致 时,版本号要用 * )。Tip #13: 在 CI 构建期间验证 composer.jsoncomposer.json 和 composer.lock 应当一直保持同步。因此,一直为他们保持自动核对是一个好主意。将此添加成为你的构建脚本的一部分将会确保 composer.lock 与 composer.json 保持同步:composer validate --no-check-all --strictTip #14: 在 PHPStorm 中使用 Composer 插件这里有一个 composer.json plugin for PHPStorm. 当手动修改 composer.json 时,插件会自动完成及执行一些验证.如果你在使用其他 IDE (或者只是一个编辑器), 你可以使用 its JSON schema 设置验证.Tip #15: 在 composer.json 中指明生产环境的 PHP 版本号如果你和我一样,有时还 在本地环境跑 PHP 最新预释版本, 那么就会处于升级依赖项的版本不能运行于生产环境的风险。现在我就在使用 PHP 7.2.0 ,也就意味着我安装的库可能在 7.1 版本中运行不了。如果生产环境跑的是 7.1 版本,安装就会失败。不过不用担心,有个非常简单的解决办法,在 composer.json 文件的 config 部分指明生产环境的 PHP 版本号即可:"config": {"platform": { "php": "7.1" }}别把它和 require 部分的设置搞混了,它的作用不同。你的应用就可以运行 7.1 或 7.2 版本下,而且同时指定了平台版本为 7.1 (这意味着依赖项的升级版本要和 平台版本 7.1 保持兼容):"require": {"php": "7.1.* || 7.2.*"},"config": {"platform": { "php": "7.1" }},Tip #16: 使用自有托管 Gitlab 上的私有包推荐使用 vcs 作为版本库类型,并且 Composer 决定获取包的合适的方法。比如,从 Github 上添加一个 fork,使用它的 API 下载整个版本库的 .zip 文件,而不用克隆。不过对一个私有的 Gitlab 安装来讲会更复杂。如果用 vcs 作版本库类型,Composer 会检测到它是个 Gitlab 类型的安装,会尝试使用 API 下载包(这要求有 API key。我不想设置,所以我只用 SSH 克隆安装了) :首先指明版本库类型是 git:"repositories": [{ "type": "git", "url": "git@gitlab.mycompany.cz:package-namespace/package-name.git" }]然后指明常用的包:"require": {"package-namespace/package-name": "1.0.0"}Tip #17: 临时使用 fork 下 bug 修复分支的方法如果在某个公共的库中找到一个 bug,并且在 Github 上自己的 fork 中修复了它, 这就需要从自己的版本库里安装这个库,而不是官方版本库(要到修复合并且修复的版本释出才行)。使用 内嵌别名 可轻松搞定:{"repositories": [ { "type": "vcs", "url": "https://github.com/you/monolog" } ], "require": { "symfony/monolog-bundle": "2.0", "monolog/monolog": "dev-bugfix as 1.0.x-dev" }}可以通过 设置 path 作为版本库类型 在本地测试这次修复,然后再 push 更新版本库。更新于 2018-01-08:文章发布后,我收到了一些建议,提供了更多的使用技巧。它们分别是:Tip #18:使用 prestissimo 加速你的包安装Composer 有个 hirak/prestissimo 插件,通过该插件能够以并行的方式进行下载,从而提高依赖包的安装速度。那么,这么好的东西,你现在该如何做?你仅仅需要马上全局安装这个插件,然后就可以自动地在所有项目中使用。composer global require hirak/prestissimoTip #19: 当你不确定时,测试你的版本约束即使在阅读 the documentation 之后,书写正确的版本约束在一些时候也是很棘手的.幸运的是,这里有 Packagist Semver Checker 可以用来检查哪个本部匹配特定的约束。他不是仅仅的分析版本约束,他从 Packagist 下载数据以来展示实际的发布版本.查看 the result for symfony/symfony:^3.1.Tip #20: 在生产环境中使用使用权威类映射文件应该在生产环境中 生成权威类映射文件 。这会让类映射文件中包含的所有类快速加载,而不必到磁盘文件系统进行任何检查。可以在生产环境构建时运行以下命令:composer dump-autoload --classmap-authoritativeTip #21: 为测试配置 autoload-dev你也不想在生产环境中加载测试文件(考虑到测试文件的大小和内存使用)。这可以通过配置 autoload-dev 解决(与 autoload 相似):"autoload": {"psr-4": { "Acme\\": "src/" }},"autoload-dev": {"psr-4": { "Acme\\": "tests/" }},Tip #22: 尝试 Composer 脚本Composer 脚本是一个创建构建脚本的轻量级工具。关于这个,我有另文述及。总结如果你不同意某些观点且阐述出你为什么不同意的意见(不要忘记标注 tip 的编号)我将很高兴。
2023年08月10日
13 阅读
0 评论
0 点赞
2023-08-10
如何防止PHP进程异常退出(进程被杀)?
如何防止PHP进程异常退出(进程被杀)?背景通常,在cli下运行的常驻后台PHP进程,可能异常退出,比如php执行过程中出现的致命错误,或被 kill 命令手动杀死等。如下面的php代码:<?phpwhile(1){$content = fgets(STDIN); if(empty($content)){ sleep(1); } //逻辑处理部分代码省略}?>查错我们使用register_shutdown_function可以跟踪到底是什么错误导致的进程退出。(想更多了解register_shutdown_function,请查看博文 妙用php中的register_shutdown_function和fastcgi_finish_request)加入了错误捕捉代码。如下:<?php$is_end = false;function catch_error(){ global $is_end; $time = date('Y-m-d H:i:s'); $error = error_get_last(); $msg = "$time [error]"; if($is_end){ $msg .= "is_end[yes]"; }else{ $msg .= "is_end[no]"; } if($error){ $msg .= var_export($error,1); } echo $msg."\r\n";}register_shutdown_function("catch_error");?>可是,php当进程再次退出时,在日志中并没有记录任何信息。说明register_shutdown_function方法根本没有执行。是什么导致register_shutdown_function方法没有运行呢?在php的官方文档中又这样一个注释:Shutdown functions will not be executed if the process is killed with a SIGTERM or SIGKILL signal. While you cannot intercept a SIGKILL, you can use pcntl_signal() to install a handler for a SIGTERM which uses exit() to end cleanly.注释的意思是当php进程获得SIGTERM和SIGKILL信号而退出时,是不执行register_shutdown_function方法的。可以使用pcntl_signal()方法来捕获信息,并调用相应的处理方法。好,如何检测是信号导致我们的php进程退出的呢?我们加入如下代码:<?phpdeclare(ticks = 1);function sig_handler($signo){ $time = date('Y-m-d H:i:s'); echo $time." exit signo[{$signo}]\r\n"; exit("");}pcntl_signal(SIGTERM, "sig_handler");pcntl_signal(SIGHUP, "sig_handler");pcntl_signal(SIGINT, "sig_handler");pcntl_signal(SIGQUIT, "sig_handler");pcntl_signal(SIGILL, "sig_handler");pcntl_signal(SIGPIPE, "sig_handler");pcntl_signal(SIGALRM, "sig_handler");?>过一段时间,发现php进程退出了,日志中出现了如下日志信息:2014-11-23 18:30:06 exit signo[14]2014-11-23 18:30:06 [error]is_end[no]看来是sigalarm信号导致php进程退出了。这个信号是可以捕获和处理的。kill 命令会发出sigalarm信号。最终的代码如下:<?phpdeclare(ticks = 1);$is_end = false;function catch_error(){ global $is_end; $time = date('Y-m-d H:i:s'); $error = error_get_last(); $msg = "$time [error]"; if($is_end){ $msg .= "is_end[yes]"; }else{ $msg .= "is_end[no]"; } if($error){ $msg .= var_export($error,1); } echo $msg."\r\n";}register_shutdown_function("catch_error");function sig_handler($signo){ $time = date('Y-m-d H:i:s'); if($signo == 14){ //忽略alarm信号 echo $time." ignore alarm signo[{$signo}]\r\n"; }else{ echo $time." exit signo[{$signo}]\r\n"; exit(""); }}pcntl_signal(SIGTERM, "sig_handler");pcntl_signal(SIGHUP, "sig_handler");pcntl_signal(SIGINT, "sig_handler");pcntl_signal(SIGQUIT, "sig_handler");pcntl_signal(SIGILL, "sig_handler");pcntl_signal(SIGPIPE, "sig_handler");pcntl_signal(SIGALRM, "sig_handler");while(1){$content = fgets(STDIN); if(empty($content)){ sleep(1); } //逻辑处理部分代码省略}$is_end = true;?>这时再kill 进程,在日志中就发现了alarm相关的日志,但是php进程依然在。PS:以上代码如果用kill -9命令杀死(SIGKILL信号),还是会退出的。也就是说SIGKILL信号是无法捕获的。参考http://blogread.cn/it/article/7256?f=wb_blogread
2023年08月10日
21 阅读
0 评论
0 点赞
2023-08-10
如何少写PHP “烂”代码
如何少写PHP “烂”代码写给初生牛犊不怕虎的童鞋们,大佬可随意摘看本章基于PHP Laravel前言经常会有人问目录如何设计比较好?代码如何分布好?怎么写一个可维护的项目?“烂”项目我也没少写,以下是参考互联网各大佬的文章总结及个人开发经验而来.Controllerclipboard.pngController顾名思义是控制器,在入门PHP的时候,就知道Controller代表MVC中的C层,MVC本身的概念就代码分离,教你如何如何将业务分开,但面临着业务的不断发展,代码的复杂度也随之提高,功能与功能之间的链接错综复杂,最后你的MVC就变成了下图,所以仅仅依托MVC的设计思想已经无法支撑不断发展的业务。现在我们将Controller的任务和能力重新定义,控制器仅仅控制Http Reqeust的请求,这样就符合了SOLID 单一功能原则.clipboard.png直接将业务代码写在Controller中,会使得代码及其臃肿,不易于维护和扩展<?phpnamespace App\Http\Controller; class UserController extends Controller{ public function register(Request $request){ $user = new User(); $user->username = $request->input('username'); $user->password = $request->input('password'); $result = $user->save(); return $result; } }这时就应该思考如何分离业务代码,我们引入Service的概念ServiceService本身译为服务将外部方法,公共方法注入到Service将Service注入到控制器clipboard.png像上图这样UserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register() { //... validation return $this->userService->register ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function register($data) { $username = $data['username']; $password = $data['password']; $password = encrypt ($password); $user = new User(); $user->username = $username; $user->password = $password; $result = $user->save(); return $result; } }到现在为止,我们至少将业务与请求彻底分开了。但还是不如人意,如果把所有的业务及CURD全部写在Service中,那只不过是将Controller的臃肿转移到了Service,那Service就没有什么存在意义了。所以我们需要继续分割Service,将对数据库的R操作独立出来,因为CUD的操作基本是一贯不变的,而R操作根据业务的复杂度则变的多姿多彩。所以独立R操作。这个时候我们引用Repository的概念。Repository我们使用Repository辅助Model,将相关的查询逻辑封装到不同的repository中,方便逻辑代码的维护符合SOLID的单一原则符合SOLID的依赖反转clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function getUserInfo() { //... validation return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public $userRepository; public function __construct(UserRepository $userRepository){ $this->userRepository = $userRepository; } public function getUserInfo() { return $this->userRepository->getUserInfo($data); } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }解决了R的问题,有人就问了,难道因为CUD比较统一简单就可以放在一起了吗?答案是NO,我们引用一个新的名词Action。Action这是看了@Charlie_Jade的文章才学到的独立每个操作文件,例如CreateUser,DeleteUser,UpdateUser符合SOLID的单一原则clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register(){ //... validation return $this->userService->register($this->request->all()); } public function getUserInfo() { return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function getUserInfo(UserRepository $userRepository) { return $this->userRepository->getUserInfo($data); } public function register(){ $result = (new CreateUser())->execute($this->request->all()); return $result; } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }CreateUser<?phpnamespace App\Action; use App\Model\Member; class CreateUser extends CreateUserWallet { public function execute(array $data) { $models = new Member(); $models->tel = $data['tel']; $models->password = $data['password']; $result = $models->save (); return $result; } }以上代码逻辑见下图clipboard.png除模版(V)等HTML,JS等,还需要一些其他的规则,或者说是方式去实现一些代码的解耦合,以下不再提供代码案例。Common译为公共的,常用的,再部分开发中,你可能需要一些公共的方法(并非公共的类,例如邮件发送等,用他并不合适),比如查询用户余额,查询用户是否注册或者是否在线,生成订单号等。使用Common更要简单。他更像一个公共函数库的样子clipboard.pngEvent不关心执行结果时可以选使用,不过Event的Listen也是提供了队列。Exception不要将你的所有错误提示都使用Return返回,很多时候你的返回未必是你的返回致谢感谢各位同学看完这篇文章,如果你有新的想法欢迎在评论区讨论.参考文章Laravel 的中大型專案架構:http://oomusou.io/laravel/arc...Laravel 程序架构设计思路使用动作类 : https://segmentfault.com/a/11...如何使用 Service 模式? : http://oomusou.io/laravel/ser...面向对象设计的SOLID原则 : https://www.cnblogs.com/shany...
2023年08月10日
18 阅读
0 评论
0 点赞
2023-08-10
PHP 应用程序安全设计指北
PHP 应用程序安全设计指北前言2018 年将至,一般程序员(特别是 Web 开发程序员)应当抛弃过去开发PHP程序的很多不好的习惯和观念了。虽然部分人不以为意,但是这确实是事实。这个指南应该以重点部分作为 PHP: The Right Way 安全章节的补充,而不是以一般的 PHP 编程话题。正文PHP 版本请在 2018 年使用 PHP 7.2, 并且计划 2019 年初切换到 PHP 7.3。PHP 7.2 已于 2017 年 11 月 30 日发布。写这篇文章的时候,只有 7.1 和 7.2 版本还在被 PHP 官方积极维护,而 5.6 和 7.0 只在大概1年内提供安全补丁更新。对于其他官方不维护的 PHP 版本,虽然某些操作系统会提供长期支持和维护,但这其实通常是有害的。尤其是他们提供安全支持补丁却没有版本号,这使得很难解释系统的安全性(仅仅知道 PHP 版本)。因此,无论其他供应商提出了什么承诺,如果可以,你就应该在任何时候都坚决地使用官方提供支持的 PHP 版本。这样,尽管最终是一个短暂的安全版本,但一个不断致力于升级的版本,总会让你收获一些意外的惊喜。依赖管理人生苦短,我用 Composer在 PHP 生态中,Composer 是最先进的依赖管理方案。我们推荐 PHP: The Right Way 中关于依赖管理的完整章节。如果你没有使用 Composer 来管理应用的依赖,最终(hopefully later but most likely sooner)会导致应用里某个依赖会严重过时,然后老旧版本中的漏洞会被利用于计算机犯罪。重要: 开发软件时,时常记得保持依赖的更新。幸运地,这只需一行命令:1composer update如果你正在使用某些专业的,需要使用 PHP 扩展(C 语言编写),那你不能使用 Composer 管理,而需要 PECL 。推荐扩展不管你正在编写什么,你总会受益于这些依赖。这是除了大多数 PHP 程序员的推荐(PHPUnit, PHP-CS-Fixer, …)外的补充。roave/security-advisoriesRoave’s security-advisories 使用 Friends of PHP repository 确保你的项目没有依赖一些已知易受攻击的依赖。1composer require roave/security-advisories:dev-master或者,你可以上传你的composer.lock文件到 Sensio Labs ,作为例行自动化漏洞评估工作流的一部分,以提醒发现任何过时的软件包。vimeo/psalmPsalm 是一个帮助你识别代码里可能存在 bugs 的静态分析工具。还有其他很好的静态分析工具(例如 Phan 和 PHPStan 都很棒),但当你发现你需要支持 PHP 5,Psalm 将是 PHP 5.4+ 的首选。使用 Psalm 挺简单:12345678Version 1 doesn't exist yet, but it will one day:composer require --dev vimeo/psalm:^0Only do this once:vendor/bin/psalm --initDo this as often as you need:vendor/bin/psalm如果你是第一次在现有代码库运行,可能会看到很多红色错误。但除非你在构建像 WordPress 那么大的程序,否则努力通过所有测试绝不是艰巨的。无论使用哪种静态分析工具,我们都推荐你能将他加入到持续集成工作流(Continuous Integration workflow)中,以便在每次更改代码中运行。HTTPS 和浏览器安全HTTPS, which should be tested, and security headers .2018 年,不安全的 HTTP 网站将不再被接受。幸运的是,由于 ACME 协议 和 Let’s Encrypt certificate authority,免费的 TLS 证书成为了可能。将 ACME 集成到你的服务器,小菜一碟。Caddy: 自动加入。Apache: 很快作为mod_md可用。在此之前,网上很多高质量教程。Nginx: 相对简单。你也许会想,“好,我已经有 TLS 证书了,为了网站变得安全和快速,得花些时间折腾配置信息。”不!Mozilla做了件好事情!。你可以根据网站的目标受众,使用配置生成器生成推荐套件。如果你希望网站安全,HTTPS ( HTTP over TLS ) 是绝对不能妥协的。使用 HTTPS 立刻就能消除多种攻击(中间人攻击、窃听、重放攻击以及若干允许用户模仿的会话形式的攻击)。安全头在服务器使用 HTTPS 确实为用户提供了许多安全性和性能方面的好处,但也还能通过利用某些浏览器的安全功能来进一步提升安全性。而这大部分会涉及到响应内容的安全头。Content-Security-Policy 1 2 3 4 5 6 7 - 你需要该 Header ,因为它提供了对于浏览器是否允许加载内部和外部资源的细化控制,从而为跨域脚本攻击漏洞提供了有效防御层。 - 参阅 [CSP-Builder](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fparagonie%2Fcsp-builder),以便快速简便地部署/管理内容安全策略(Content Security Policies)。 - 为了更加深入的分析, Scott Helme's [introduction to Content-Security-Policy headers](https://link.juejin.im/?target=https%3A%2F%2Fscotthelme.co.uk%2Fcontent-security-policy-an-introduction%2F),会是一个很好的引导。 - ```http Expect-CT 你需要该 Header ,因为它能通过强制某些不良行为者将其错误证书的证据颁发到可公开验证的仅可追加的数据结构,从而针对流氓/受损的证书颁发机构增加一层防护。 优先设置为enforce,max-age=30。只要你有足够的自信该 Header 不会造成服务中断,增加max-age吧。Referrer-Policy1234567你需要该 Header ,因为它允许你控制用户的行为信息是否泄露给第三方。同样地,Scott Helme 提供了一篇关于Referrer-Policy Header 介绍好文。除非有理由允许更加宽松的设置,否则请设置为same-origin或no-referrer。Strict-Transport-Security 你需要该 Header ,因为它告诉浏览器通过 HTTPS 而不是不安全的 HTTP ,将 future requests 设为同源。 在第一次部署时,将其设置为max-age = 30,然后当你确信没有任何内容会中断时,将此值增加到某个较大的值(例如 31536000)。X-Content-Type-Options123456你需要该 Header ,因为 MIME 类型的混淆可能会导致不可预知的结果,包括奇怪的允许 XSS 漏洞的边缘情况。这最好伴随着一个标准的 Content-Type Header 。除非需要默认的行为(例如文件的下载),否则请设置为nosniff。X-Frame-Options 你需要该 Header ,因为它允许你防止点击劫持。 设置为DENY (或者SAMEORIGIN, 但仅仅当你使用<frame>元素的时候)。X-XSS-Protection123456789101112你需要该 Header ,因为它启用了一些默认情况下未启用的浏览器反 XSS 功能。设置为1; mode=block。同样,如果你使用 PHP 的内置会话管理功能(建议使用),则可能需要这样调用session_start():<?php session_start([ 'cookie_httponly' => true, 'cookie_secure' => true ]); 这会强制你的应用在发送会话标识符时使用 HTTP-Only 和 Secure 标志,从而防止 XSS 攻击窃取用户的 Cookie ,并强制它们分别通过 HTTPS 发送。 我们之前在 2015 年的博客文章中介绍了安全的 PHP 会话。 子资源完整性 在将来的某个时候,你也许会使用 CDN 来加载网站的公共 JavaScript/CSS 库。安全工程师已经遇见了这存在一个明显的风险,如果很多网站使用 CDN 提供内容,Hack 和替换 CDN(获得了 CDN 的控制权)就可以注入(恶意)代码到成千上万的网站。 查阅子资源完整性吧。 子资源完整性(SRI,Subresource integrity)允许你将希望 CDN 服务的文件的内容进行哈希处理。目前实行的 SRI 只允许使用安全的密码散列函数,这意味着攻击者不可能生成与原始文件哈希相同的恶意版本资源。 一个真实例子: Bootstrap v4-alpha uses SRI in their CDN example snippet 1 2 3 4 5 6 7 8 9 10 11 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous" /> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous" ></script> 文档关系 Web 开发人员经常在超链接上设置目标属性(例如,target ="_ blank"在新窗口中打开链接)。但是,如果你没有传递rel ="noopener"标签,则可以允许目标页面控制当前页面。 不要这样做: 1 <a href="http://example.com" target="_blank">Click here</a> 这会让http://example.com页面能控制当前页面。 而应该这样做: 1 <a href="https://example.com" target="_blank" rel="noopener noreferrer">Click here</a> 通过这样在新窗口打开https://example.com,当前窗口的控制权也不会授予可能的恶意第三方。 可以更加深入研究。 开发安全的 PHP 程序 如果应用程序安全性对你来说是一个新话题,请从应用程序安全性简介开始吧。 大多数安全专家指出,开发者可以使用 OWASP Top 10 等资源开始着手。 但是,大多数常见的漏洞也可以是相同高等级的安全问题(例如代码和数据没有完全分离、逻辑不严谨和健全、操作环境不安全或是可破译的密码协议等)。 我们的假设是,应该授予安全新手知道一些更简单、基础的安全知识和问题,并如何解决这些问题,应该是一个更好的、长远的安全工程。 因此,我们避免推荐十大或二十大安全清单。 数据库注入 避免 PHP 程序存在 SQL 注入。 如果你是自己编写 SQL 代码,请确保使用prepared语句,并且从网络或文件系统提供的信息都作为参数传递,而不是字符串拼接的形式。此外,确保你没有使用模拟的prepared语句。 为了达到好的效果,可以使用 EasyDB 。 不要这样做: 1 2 3 <?php /* Insecure code: */ $query = $pdo->query("SELECT * FROM users WHERE username = '" . $_GET['username'] . "'"); 应该这样做: 1 2 3 <?php /* Secure against SQL injection: */ $results = $easydb->row("SELECT * FROM users WHERE username = ?", $_GET['username']); 还有其他数据库抽象层提供了相同的安全性(EasyDB实际上是在使用 PDO ,但在实际的prepare语句前避免了prepared语句模拟)。 只要用户输入不会影响查询的结构,就很安全(包括存储过程)。 文件上传 深入:如何安全地允许用户上传文件? 接受文件上传是一个冒险的提议,但只要采取一些基本的预防措施,是能保证安全的。也就是说,允许文件直接上传的话,这些文件可能会被意外的允许执行或解释。上传的文件应该是只读(read-only)或读写(read-write)的,永远不应该可执行(executable)。 如果你的网站根目录是/var/www/example.com,请不要保存上传文件在/var/www/example.com/uploaded_files。 而应该保存到一个不能直接访问的目录(例如:/var/www/example.com-uploaded/),以免意外地将其作为服务器端脚本执行,并获得执行远程代码的后门。 一个更加简洁的方法是将网站根目录往下移动一个层级(即:/var/www/example.com/public)。 如何安全地下载这些上传文件也是一个问题。 直接访问 SVG 图像类型时,将在用户浏览器执行 JavaScript 代码。尽管它的MIME类型中的image/前缀具有误导性,但是这是正确的。 正如前面提及的,MIME 类型嗅探可能导致类型混淆攻击。请参阅X-Content-Type-Options。 如果你放弃前面关于如何安全地存储上传文件的建议,攻击者就会通过上传 .php 或 .phtml 文件,直接在浏览器中访问文件来执行任意代码,从而完全控制服务器。 跨站脚本 关于 PHP 中的跨站脚本攻击,你想知道的都在这里 同样地,预防 XSS 和 SQL 注入是一样简单的。我们有简单而易用的 API 来分离文档结构(structure of a document)和填充的数据。 然而,实际上还有很多 Web 开发程序员仍是通过生成一大串 HTML 代码作为响应的形式开发。并且,这不是 PHP 独有的现实,这是所有 Web 开发程序员都应该重视的。 减少 XSS 漏洞不失为一个好方法。总之,前面谈及的浏览器安全的章节就显得十分相关了。简言之: 尽量避免输出和输入(Always escape on output, never on input)。如果你把已清洗的数据(sanitized data)保存在数据库,然后在其它地方被发现了 SQL 注入漏洞,攻击者将通过恶意程序污染这些受信任的已清洗数据(trusted-to-be-sanitized record),从而绕开 XSS 保护。 如果你的框架有一个提供自动上下文过滤的模板引擎,那就使用它吧。这些工作可由框架安全地做到。 echo htmlentities($ string,ENT_QUOTES | ENT_HTML5,'UTF-8') 是一种安全、有效的方法阻止UTF-8编码的网页上的所有 XSS 攻击,但不是任何 HTML 都有效。 如果你的环境要求你使用 Markdown 而不是 HTML ,那就不要使用 HTML 了。 如果你需要使用原生 HTML(没有使用模板引擎),参阅第一点,并且使用 HTML Purifier 吧。HTML Purifier 不适合转义为 HTML 属性上下文(HTML attribute context)。 跨站请求伪造 跨站请求伪造(CSRF)是一种混淆的代理攻击,通过诱导用户的浏览器代表攻击者执行恶意的 HTTP 请求(使用的是该用户的权限)。 这在一般情况下是很容易解决的,只需两步: 使用 HTTPS 。这是先决条件。没有 HTTPS 的话,任何保护措施都是脆弱的,虽然 HTTPS 本身并不防御 CSRF 。 增加基本的 Challenge-response authentication。 为每个表单添加一个隐藏的表单属性。 填充一个密码安全的随机值(称为令牌)。 验证是否提供了隐藏的表单属性,以及是否匹配上期望值。 我们写了一个名为 Anti-CSRF 的库,并且: 你可以使每个令牌只能使用一次,以防止重放攻击。 多个令牌存储在后端。 一旦令牌获取完,令牌会循环使用。 每个令牌可以绑定特定的 URL 。 如果某个令牌泄露了,它不能在不同的上下文使用。 令牌可以绑定特定的 IP 地址。 v2.1 后,令牌可以重复使用(例如供 Ajax 使用)。 如果你没有使用防止 CSRF 漏洞的框架,请将 Anti-CSRF 放在一边。在不久的将来,SameSite cookies将允许我们更简单地避免CSRF攻击。 XML 攻击 (XXE, XPath Injection) 在处理大量 XML 的应用程序中存在两个主要的漏洞: XML External Entities (XXE) XPath 注入 除此之外, XXE 攻击可用作包含攻击代码的本地/远程文件的启动器。 早期版本的 Google Docs 被着名于 XXE ,但除了在很大程度上使用 XML 的商业应用程序之外,基本闻所未闻。 针对 XXE 袭击的主要缓解措施: 1 2 <?php libxml_disable_entity_loader(true); 除 XML 文档外,XPath注入与 SQL 注入非常相似。 幸运的是,将用户输入传递给 XPath 查询的情况在 PHP 生态中非常罕见。 而不幸的是,这也意味着 PHP 生态中不存在可用的最佳避免措施(预编译和参数化 XPath 查询)。最好的办法是在任何涉及 XPath 查询的数据上设置允许使用的字符白名单。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php declare(strict_types=1); class SafeXPathEscaper { /** * @param string $input * @return string */ public static function allowAlphaNumeric(string $input): string { return \preg_replace('#[^A-Za-z0-9]#', '', $input); } /** * @param string $input * @return string */ public static function allowNumeric(string $input): string { return \preg_replace('#[^0-9]#', '', $input); } } // Usage: $selected = $xml->xpath( "/user/username/" . SafeXPathEscaper::allowAlphaNumeric( $_GET['username'] ) ); 白名单总会比黑名单更安全。 反序列化和 PHP 对象注入 深入: 在PHP中安全地实现(反)序列化 如果你将不可信的数据传递给unserialize(),则通常是这两个结果之一: PHP 对象注入,它能用于启动 POP 链(POP chain)并触发其他误用对象的漏洞。 PHP 解释器本身的内存损坏。 大多数开发人员更喜欢使用JSON序列化,这是对其软件安全状况的显著改进。但请记住,json_decode()容易受到散列冲突拒绝服务(Hash-DoS)攻击。不幸的是,PHP的Hash-DOS问题还没有得到彻底解决。 从djb33迁移到Siphash,对于字符串输入,哈希输出的最高位设置为 1 ,对于整数输入设置为 0 ,使用CSPRNG提供的请求密钥,将完全解决这些攻击。 不幸的是, PHP 团队还没有准备好放弃他们已经在 PHP 7 系列中取得的性能提升,所以很难说服他们放弃 djb33 (这是非常快但不安全的) 赞成 SipHash (这也是快速的,但不像 djb33 那么快,但更安全)。 如果性能受到重大影响,可能会阻碍未来版本的采用,但也影响了安全性。 因此,最好的办法是: 使用JSON,因为它比unserialize()更安全。 在任何可能的地方,确保输入在反序列化之前被认证。 对于提供给用户的数据,通过一个只有服务器知道的秘钥使用sodium_crypto_auth()和sodium_crypto_auth_verify()验证。 对于第三方提供的数据,让他们使用 1 sodium_crypto_sign() 签名他们的 JSON 消息,然后使用 1 sodium_crypto_sign_open() 和第三方公钥验证消息。 如果你需要对传输的签名进行十六进制或 Base64 位编码,也可以使用分离的签名 API 。 如果你无法验证 JSON 字符串,请严格限制速度并阻止 IP 地址,以减轻重复的违规者。 密码散列 深入:2016 年,如何安全地保存用户密码 安全的密码存储曾经是一个激烈争论的话题,但现在实现起来相当微不足道,特别是在 PHP 中: 1 2 3 4 5 6 7 8 9 <?php $hash = \password_hash($password, PASSWORD_DEFAULT); if (\password_verify($password, $hash)) { // Authenticated. if (\password_needs_rehash($hash, PASSWORD_DEFAULT)) { // Rehash, update database. } } 你甚至不需要知道在后台使用什么算法,因为如果你使用最新版本的 PHP ,你也将使用当前最新的技术,用户的密码将会自动进行升级(只要有新的默认算法可用)。 无论你做什么,都不要做 WordPress 所做的事情。 从 PHP 5.5 到 7.2 ,默认算法都是 Bcrypt 。在未来,它可能会切换到获得密码哈希大赛冠军的 Argon2 。 如果你以前没有使用password_* API ,那需要迁移遗留哈希,请确保以这种方式进行。很多公司搞错了, 最有名的是雅虎。 最近,错误地实施传统哈希升级似乎导致了苹果的iamroot错误。 通用加密 这是一些我们详细写了的话题: Using Encryption and Authentication Correctly (2015) Recommended: Choosing the Right Cryptography Library for your PHP Project: A Guide (2015) Recommended: You Wouldn’t Base64 a Password - Cryptography Decoded (2015) Cryptographically Secure PHP Development (2017) Recommended: Libsodium Quick Reference: Similarly-Named Functions and Their Use-Cases (2017) 一般来说,你总是希望使用 Sodium cryptography library(libsodium)进行应用层加密。如果你需要支持早于 7.2 的 PHP 版本(像 5.2.4),你可以使用sodium_compat,基本上可以假设你的用户也是 7.2 。 在特定情况下,由于严格的算法选择和互操作性,你可能需要不同的库。如有疑问,请咨询密码专家和密码工程师,了解密码选择是否安全(这是我们提供的服务之一)。 随机性 深入:如何在 PHP 中生成安全的整数和字符串? 如果你需要随机数字,请使用random_int()。如果你需要随机字节字符串,请使用random_bytes()。不要使用mt_rand(),rand()或uniqid()。 如果你需要从秘密种子(secret seed)生成伪随机数(pseudorandom),请使用SeedSpring,而不是srand()或mt_srand()。 1 2 3 4 5 6 7 8 <?php use ParagonIE\SeedSpring\SeedSpring; $seed = random_bytes(16); $rng = new SeedSpring($seed); $data = $rng->getBytes(1024); $int = $rng->getInt(1, 100); 服务器端 HTTPS 请求 确保 TLS 证书验证没有被禁用 随意使用你已经熟悉的任何兼容 PSR-7 的 HTTP 客户端。 我们喜欢 Guzzle ,有些人喜欢直接使用 cURL 。 无论你最终使用什么,请确保使用的确定性,以确保始终可以拥有最新的 CACert 软件包,从而允许启用最严格的 TLS 证书验证设置并保护服务器的出站 HTTPS 请求。 安装 Certainty 很简单: 1 composer require paragonie/certainty:^1 使用 Certainty 也很简单: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php use ParagonIE\Certainty\RemoteFetch; $latestCACertBundle = (new RemoteFetch())->getLatestBundle(); # cURL users: $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_CAINFO, $latestCACertBundle->getFilePath()); # Guzzle users: /** @var \GuzzleHttp\Client $http */ $repsonse = $http->get( 'https://example.com', [ 'verify' => $latestCACertBundle->getFilePath() ] ); 这样可以保护你免受网络服务器与集成的任何第三方 API 之间的中间人攻击。 我们真的需要 Certainty 吗? 保护你的系统, Certainty 并不是严格的要求。缺少它并不是什么漏洞。但如果没有 Certainty ,开源软件必须猜测操作系统的 CACert 软件包的存在位置,如果猜测错误,它往往会失败并导致可用性问题。从历史上看,这激励了许多开发人员只是禁用证书验证,以便他们的代码“正常工作”,却没有意识到他们只是将应用程序变成主动攻击。 Certainty 通过将 CACert 捆绑在最新的可预测位置来消除这种激励。 Certainty 还为希望运行自己的内部 CA 为企业提供大量的工具。 谁禁用了证书验证? 流行的内容管理系统(WordPress,Magento 等 CMS)的插件/扩展开发者!这是我们试图在生态系统层面上解决的一个巨大的问题。 它不是孤立的任何特定的 CMS ,你会发现这些不安全的插件等都是类似的。 如果使用了类似的 CMS ,请在插件中搜索CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST,你可能会发现有几个将这些值设置为FALSE。 避免的事情 不要使用mcrypt。这是一个十多年来没有开发出来的密码学库。如果你遵循我们的 PHP 版本建议,这应该是一个容易避免的错误,因为mcrypt不再被 PHP 7.2 和更新的版本支持。 配置驱动的安全建议应该大部分地忽略。如果你正在阅读 PHP 安全性指南,并告诉你更改 php.ini 设置而不是编写更好的代码,那么你可能正在阅读过时的建议。关闭窗口并转到一些和register_globals无关的文章上吧。 不要使用 JOSE(JWT,JWS,JWE),这是一套互联网标准,它编纂了一系列容易出错的密码设计。尽管由于某种原因,被写入了标准,也吸引了很多传道人。 加密 URL 参数是公司常用来模糊元数据的反模式(例如,我们有多少用户?)。 它带来了实施错误的高风险,也造成了错误的安全感。我们在链接的文章中提出了一个更安全的选择。 除非迫不得已,否则不要提供“我忘记了我的密码”的功能。 不要讳言:密码重置功能是一个后门。 有一些方法可以实施以抵御合理的威胁模型,但高风险用户应该不被考虑。 避免使用 RSA,改用 libsodium 。如果你必须使用 RSA ,请确保指定 OAEP 填充。 1 2 3 4 5 6 7 8 <?php openssl_private_decrypt( $ciphertext, $decrypted, // Plaintext gets written to this variable upon success, $privateKey, OPENSSL_PKCS1_OAEP_PADDING // Important: DO NOT OMIT THIS! ); 如果你不得不使用 PKCS#1 v1.5 填充,那么无论你与哪个集成在一起,几乎肯定会受到 ROBOT 的影响,请以允许明文泄露和签名伪造的漏洞将其报告给相应的供应商(或 US-CERT )。 专业用法 现在你已经掌握了在 2018 年及以后构建安全 PHP 应用程序的基础知识,接下来我们来看一些更专业的用法。 可搜索的加密 深入:使用PHP和SQL构建可搜索的加密数据库 可搜索的加密数据库是可取的,但被广泛认为是不太可能实现的。上面链接的博客文章试图通过改进我们解决方案来实现,但本质上是这样的: 设计你的架构,以便数据库(database compromise)不会让攻击者访问你的加密密钥。 用一个密钥加密数据。 基于 HMAC 或具有静态盐的安全 KDF (secure KDF with a static salt)创建多个索引(具有自己独特的密钥) 可选:截断步骤3的输出,将其用作布隆过滤器(Bloom filter) 在 SELECT 查询中使用步骤3或4的输出 解密结果。 在这个过程中的任何一步,你都可以根据实际使用情况进行不同的权衡。 没有 Side-Channels 的基于令牌的身份验证 深入: Split Tokens: Token-Based Authentication Protocols without Side-Channels 说到数据库(上一节),你是否知道 SELECT 查询理论上可能是定时信息泄漏的来源? 简单的缓解措施: 把你的认证令牌分为两半 一半在 SELECT 查询中使用 后一半在恒定的时间(constant-time)验证 可以选择将后半部分的散列存储在数据库中。这对于只能使用一次的令牌是有意义的,例如 密码重置或“在此计算机上记住我”的令牌 即使可以使用定时泄漏来窃取一半的令牌,剩下的也需要暴力破解才能成功。 开发安全的API 深入: Hardening Your PHP-Powered APIs with Sapient 我们写了 SAPIENT (the Secure API ENgineering Toolkit),让服务器到服务器验证的消息传递变得简单易行。除了 HTTPS 提供的安全性之外,Sapient允许你使用共享密钥或公钥来加密和验证消息。 这使得即使存在中间攻击者,并设有流氓证书颁发机构,你也可以使用Ed25519对 API 请求和响应进行身份验证,或者将消息加密到只能由接收方服务器的密钥解密的目标服务器。 由于每个 HTTP 消息体都通过安全密码进行身份验证,所以可以安全地使用它来代替stateful token juggling protocols(例如 OAuth)。但是,在密码学方面,在做任何不规范的事情之前,总要确保他们的实现是由专家研究的。 所有Sapient使用的密码算法都由Sodium cryptography library提供。 进一步阅读: Sapient Documentation Sapient Tutorial Sapient Specification Paragon Initiative Enterprises已经在其许多产品(包括许多开源软件项目)中使用了Sapient, 并将继续添加软件项目到Sapient用户群中。 使用Chronicle记录安全事件 深入: Chronicle Will Make You Question the Need for Blockchain Technology Chronicle是一个基于散列链数据结构的仅追加密码分类账(append-only cryptographic ledger),具有很多吸引公司“区块链”技术的属性,而不会过分矫枉过正。 除了仅追加密码分类账(append-only cryptographic ledger)这个具有创造性的用例之外,Chronicle集成到SIEM中时,也可以十分有亮点,因为你可以将安全关键事件发送到私人Chronicle中,并且它们是不能被改变的。 如果你的Chronicle设置为将其摘要散列交叉签名到其他Chronicle实例,或者如果有其他实例配置为复制你的Chronicle内容,攻击者就很难篡改你的安全事件日志。 在Chronicle的帮助下,你可以获得区块链所承诺的弹性特性(resilience),而没有任何隐私,性能或可伸缩性问题。 要将数据发布到本地Chronicle,你可以使用任何与Sapient-compatible API,但最简单的解决方案称为Quill。 作者的一些话 一些聪明的读者可能注意到我们引用了很多我们自己的工作,包括博客文章和开源软件。(当然也不仅仅引用了我们自己的工作) 这绝不是偶然的。 自从我们在 2015 年初成立以来,一直在编写安全库并参与提高 PHP 生态系统安全性的工作。我们已经涉足了很多领域,而且我们的安全工程师(他们最近推动了更安全的加密技术加入 PHP 核心,就在最近的 PHP 7.2 中)自我担保地说,并不擅长自我炒作,或是对已经做过的工作持续热情。但你很可能没有听说我们多年来开发的工具或库。对于这个,深感抱歉。 不论如何,我们也不可能成为各方面的先行者,所以我们尽可能地选择与重视公共利益而不是贪图小利的行业专家工作。 这也是为什么浏览器安全的许多章节都参考了 Scott Helme 和他公司的工作,他们在为开发人员提供这些新的安全功能方面具有可访问性和可理解性。 本指南当然不会是详尽的。编写不安全代码的方法几乎和编写代码的方法一样多。 安全是一种心态,而不是目的地。 随着上面所写的一切,以及后面涉及的资源,我们希望这将有助于全世界的开发人员,从今天开始用 PHP 编写安全的软件。 资源 如果你已经按照本页上的所有内容进行了操作,并且需要更多内容,则可能会对我们策划的阅读列表感兴趣,以便学习应用程序安全性。 如果你认为自己编写的代码足够安全,并希望我们从安全工程师的角度对其进行评判,这也是我们为客户提供的服务。 你如果为一家要进行合规性测试(PCI-DSS,ISO 27001等)的公司工作,可能还想聘请我们公司来审核你的源代码。我们的流程比其他安全咨询公司更适合开发者。 接下来是 PHP 和信息安全社区提供的资源列表,这些资源帮助互联网更加安全。 PHP: The Right Way:现代 PHP 开发的实用指南,免费在线。 Mozilla’s SSL Config Generator Let’s Encrypt:证书颁发机构,通过提供免费 TLS 证书,为创建更安全的 Internet 做了很多。 Qualys SSL Labs:为 TLS 配置提供了一个快速而简单的测试套件。几乎每个人都使用这个来解决他们的密码组和证书问题,理由很充分:It does its job well. Security Headers:可以检验你的网站在使用浏览器安全功能来保护用户方面的表现如何。 Report-URI:一个很好的免费资源,提供监控 CSP/HPKP 等安全策略的实时安全报告服务。他们给你一个 Report-URI,你可以传递给你的用户的浏览器,如果有什么事情发生或有人发现 XSS 攻击媒介,他们会投诉Report-URI。 Report-URI 会汇总这些错误,并允许你更好地对这些报告进行疑难解答和分类。 PHP Security Advent Calenda:RIPSTech旗下的团队负责。 Snuffleupagus:一个面向安全的 PHP 模块(Suhosin的精神继承者,似乎在很大程度上会被放弃) PHP Delusions:一个致力于更好地使用 PHP 的网站。大部分的口吻是非常有见地的,作者对技术的准确性和清晰度的奉献使得值得一读,特别是对于那些不太喜欢 PDO 功能的人来说。 Have I Been Pwned?:帮助用户发现他们的数据是否属于过时数据泄露。 结尾 原文地址:The 2018 Guide to Building Secure PHP Software - P.I.E. Staff 最早是在Laravel China社区里帖子 - The 2018 Guide to Building Secure PHP Software看到,一位同学只发了原链接,由于是全英,文章也比较长,就没有深读,但可以知道这是一篇很好的文章,值得学习,这几天花了时间翻译了全文。
2023年08月10日
11 阅读
0 评论
0 点赞
2023-08-10
PHP异步编程简述
PHP异步编程简述概述异步编程,我们从字面上理解,可以理解为代码非同步执行的。异步编程可以归结为四种模式:回调、事件监听、发布/订阅、promise模式。我们最熟悉的两种模式是回调和事件监听,举两个最简单的javascript例子,一个ajax,一个点击事件的绑定:123$.getJSON("uri", params, function(result) {do_something_with_data(result);});123$("#id").click(function(){do_something_when_user_click_id();});以上两个示例有一个共同的特点,就是把函数当做参数传递给另一个函数。被传递的函数可以被称作为闭包,闭包的执行取决于父函数何时调用它。优势与劣势异步编程具有以下优势:解耦,你可以通过事件绑定,将复杂的业务逻辑分拆为多个事件处理逻辑并发,结合非阻塞的IO,可以在单个进程(或线程)内实现对IO的并发访问;例如请求多个URL,读写多个文件等效率,在没有事件机制的场景中,我们往往需要使用轮询的方式判断一个事件是否产生异步编程的劣势:异步编程的劣势其实很明显——回调嵌套。相信一部分人在写ajax的时候遇到过这样的场景:1234567$.getJSON("uri", params, function(result_1) {$.getJSON("uri", result_1, function(result_2) { $.getJSON("uri", result_2, function(result_3) { do_something_with_data(result_3); }); });;});这样的写法往往是因为数据的依赖问题,第二次ajax请求依赖于第一次请求的返回结果,第三次ajax依赖于第二次。这样就造成深层次的回调嵌套,代码的可读性急剧下降。虽然有一些框架能够通过一些模式解决这样的问题,然并卵,代码的可读性相比同步的写法依然差很多。异步编程的另一个劣势就是编写和调试的过程更加复杂,有时候你不知道什么时候你的函数才会被调用,以及他们被调用的顺序。而我们更习惯同步串行的编程方式。然而,我相信一旦你开始使用异步编程,你一定会喜欢上这种方式,因为他能够带给你更多的便利。PHP异步编程概述在php语言中,异步的使用并不像javascript中那么多,归其原因主要是php一般是在web环境下工作,接收请求->读取数据->生成页面,这看起来天生就是一个串行的过程;所以,在php中,异步并没有广泛使用。在javascript中的4中异步编程模式,均可以在php中实现。回调:1234array_walk($arr, function($key, $value){$value += 1;});print_r($arr);回调的方式,在大多情况下,代码仍然是顺序执行的(array_walk->print_r的顺序)。回调函数的意义在于被传递者可以调用回调函数对数据进行处理,这样的好处在于提供更好的扩展性和解耦。我们可以把这个回调函数理解为一个格式化器,处理相同的数据,当我传递一个json过滤器时,返回的结果可能是一个json压缩过的字符串,当我传递的是一个xml过滤器时,返回的结果可能是一个xml字符串(有点多态的思想)。事件监听(定时器,时间事件):12345678$loop = React\EventLoop\Factory::create();$loop->addPeriodicTimer(5, function () {$memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n";});$loop->run();事件监听在PHP中用的并不多,但并不是没有,例如pcntl_signal()监听操作系统信号,以及其他IO事件的监听等等。上面的示例是一个事件事件的侦听,每隔5s中,会执行一次回调函数。在四种异步模式中,事件监听的应用是更有意义的。然我们看一个同步的例子,下面这段代码用于向百度和google(一个不存在的网站)发起请求,同步的编写写法是先去请求百度或者google,等待请求结束后再请求另一个:123$http = new HTTP();echo $http->get('http://www.baidu.com');echo $http->get('http://www.google.com');基于事件的处理方式可以是这样的:1234567$http = new HTTP();$http->get('www.baidu.com');$http->get('www.huyanping.cn');$http->on('response', function($response){echo $response . PHP_EOL;});$http->run();异步的写法允许我们同时处理多个事务,谁先完成,就先去处理谁。一个简单的异步http客户端见:async-http-phpPHP有很多扩展和包提供了这方面的支持:ext-libevent libevent扩展,基于libevent库,支持异步IO和时间事件ext-event event扩展,支持异步IO和时间事件ext-libev libev扩展,基于libev库,支持异步IO和时间事件ext-eio eio扩展,基于eio库,支持磁盘异步操作ext-swoole swoole扩展,支持异步IO和时间,方便编写异步socket服务器,推荐使用package-react react包,提供了全面的异步编程方式,包括IO、时间事件、磁盘IO等等package-workerman workerman包,类似swoole,php编写发布/订阅:12345$lookup = new nsqphp\Lookup\Nsqlookupd;$nsq = new nsqphp\nsqphp($lookup);$nsq->subscribe('mytopic', 'somechannel', function($msg) {echo $msg->getId() . "\n";})->run();promise:12345678910111213141516171819202122232425function getJsonResult(){return queryApi() ->then( // Transform API results to an object function ($jsonResultString) { return json_decode($jsonResultString); }, // Transform API errors to an exception function ($jsonErrorString) { $object = json_decode($jsonErrorString); throw new ApiErrorException($object->errorMessage); } );}// Here we provide no rejection handler. If the promise returned has been// rejected, the ApiErrorException will be throwngetJsonResult()->done(// Consume transformed object function ($jsonResultObject) { // Do something with $jsonResultObject });promise模式的意义在于解耦,就在刚刚我们提到的异步回调嵌套的问题,可以通过promise解决。其原理是在每一次传递回调函数的过程中,你都会拿到一个promie对象,而这个对象有一个then方法,then方法仍然可以返回一个promise对象,通过传递promise对象可以实现把多层嵌套分离出来。具体的代码需要去研究一下源码才可以,有点难懂,PHP的promise推荐阅读:promise异步的实现原理异步的实现大多情况下少不了循环监听事件,例如我们上面看到$loop->run(),这里其实是一个死循环,监听到事件则调用相应的处理函数。如果你对pcntl熟悉,你一定知道declare(tick=1),其实它也是一种循环,含义是每执行tick行代码,则检查一次是否有尚未处理的信号。虽然会有一个阻塞的死循环(大多数情况下,declare属于特殊情况),但我们可以对多个事件进行监听处理,同时可以在某一个事件处理的过程中停止循环,这样就可以实现并发异步的IO访问,甚至更多。一段伪代码如下:12345678910$async = new Async();$async->on('request', function($requset){do_something_with($request);});// 这里其实就是$loop->run()的核心代码while(true){$async->hasRequest() ? $async->callRequestCallback() : null; sleep(1);}总结整片文章其实并不够详细,充其量算是一篇介绍性的文章,算是我在异步编程方面的一次总结。异步编程的学习并不像学习一门语言或者设计模式那样简单,它要求我们改变传统的编程方式。而异步IO对于学习者要求也略高,首先你必须熟悉同步的IO操作,甚至你需要了解一些协议解析的内容。希望上面的内容对于初学者有一些帮助。文中若有错误的地方,还望指正。
2023年08月10日
14 阅读
0 评论
0 点赞
1
...
56
57
58
...
112