首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
138 阅读
2
php接口优化 使用curl_multi_init批量请求
130 阅读
3
2024年备考系统架构设计师
102 阅读
4
《从菜鸟到大师之路 ElasticSearch 篇》
102 阅读
5
PHP 文件I/O
89 阅读
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
累计撰写
785
篇文章
累计收到
7
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
785
篇与
的结果
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 用websocket实现客户端和服务器消息双向推送
PHP 用websocket实现客户端和服务器消息双向推送PHP 实现websockethtml代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>发送弹幕</title> <link href="https://cdn.bootcss.com/<a class="wpal-linked-keyword" href="https://bootstrap.p2hp.com/" target="_blank">bootstrap</a>/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="row"> <div class="col-xs-1 col-sm-1 col-md-1 col-lg-1"> </div> <div class="col-xs-10 col-sm-10 col-md-10 col-lg-10"> <div class="form-group"> <p style="height:30px"></p> <div class="col-sm-2"> </div> <div class="col-sm-4"> <input type="text" class="form-control" id="barrage" name="barrage" placeholder="弹幕" value=""> </div> <div class="col-sm-4"> <button type="button" class="btn btn-primary" id="send">发送弹幕</button> </div> </div> <!-- 弹幕内容 --> <div class="form-group"> <p style="height:30px"></p> <textarea class="form-control" rows="20" id="content"></textarea> </div> </div> <div class="col-xs-1 col-sm-1 col-md-1 col-lg-1"> </div> </div> </div> </body> <script> $(document).ready(function () { var ws = new <a class="wpal-linked-keyword" href="https://websocket.p2hp.com/" target="_blank">WebSocket</a>("ws://127.0.0.1:9777"); ws.onopen = function () { console.log("握手成功"); } ws.onmessage = function (e) { var content = e.data; $('#content').append(content + "\n"); console.log(content); } ws.onerror = function () { console.log("error"); } $('#send').click(function (e) { e.preventDefault(); var barrage = $('#barrage').val(); ws.send(barrage); }); $('#barrage').bind('keypress', function (event) { if (event.keyCode == "13") { var barrage = $('#barrage').val(); ws.send(barrage); } }); }); </script> </html>PHP代码<?php class <a class="wpal-linked-keyword" href="https://socketio.p2hp.com/" target="_blank">Socket</a> { const BIND_NUM = 20; private $master; private $sockets = []; private $handshake = false; // 握手 public function __construct($address, $port) { try { // 创建 $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 参数 socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1); socket_set_nonblock($this->master); // 绑定 socket_bind($this->master, $address, $port); // 监听 socket_listen($this->master, static::BIND_NUM); $this->sockets[] = $this->master; $pid = posix_getpid(); // 输出 $this->say("Server Started : " . date('Y-m-d H:i:s')); $this->say("Listening on : " . $address . " port " . $port); $this->say("Pid : " . $pid); $this->say("Master socket : " . $this->master . PHP_EOL); } catch (\Exception $e) { $this->error(); } while (true) { try { // 慢点 usleep(200000); $this->doServer(); } catch (\Exception $e) { $this->error(); } } } /** * 开始服务 */ public function doServer() { $write = $except = NULL; socket_select($this->sockets, $write, $except, NULL); //自动选择来消息的socket 如果是握手 自动选择主机 foreach ($this->sockets as $socket) { // 主机 if ($this->master == $socket) { $client = socket_accept($this->master); if ($client < 0) { $this->notice("socket_accept() failed"); continue; } else { $this->connect($client); } } else { // 非主机 $bytes = socket_recv($socket, $buffer, 2048, 0); if ($bytes == 0) { // 断开连接 $this->disConnect($socket); } else { if (!$this->handshake) { // 准备握手 $this->doHandShake($socket, $buffer); } else { // 发送消息 $buffer = $this->decode($buffer); $buffer='server say:'.$buffer; $this->send($socket, $buffer); } } } } } /** * 连接 * * @param $socket */ public function connect($socket) { array_push($this->sockets, $socket); $this->say("\n" . $socket . " CONNECTED!"); $this->say(date("Y-n-d H:i:s")); } /** * 断开连接 * * @param $socket */ public function disConnect($socket) { $index = array_search($socket, $this->sockets); socket_close($socket); $this->say($socket . " DISCONNECTED!"); if ($index >= 0) { array_splice($this->sockets, $index, 1); } } /** * 握手 * * @param $socket * @param $buffer * @return bool */ function doHandShake($socket, $buffer) { $this->say("\nRequesting handshake..."); $this->say($buffer); $key = ''; if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) { $key = $match[1]; } $this->say("Handshaking..."); $upgrade = "HTTP/1.1 101 Switching Protocol\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $this->calcKey($key) . "\r\n\r\n"; //必须以两个回车结尾 $this->say($upgrade); socket_write($socket, $upgrade, strlen($upgrade)); $this->handshake = true; $this->say($key); $this->say("Done handshaking..."); return true; } /** * 基于websocket version 13 * * @param $key * @return string */ function calcKey($key) { $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); return $accept; } /** * 解密 * * @param $buffer * @return null|string */ function decode($buffer) { $len = $masks = $data = $decoded = null; $len = ord($buffer[1]) & 127; if ($len === 126) { $masks = substr($buffer, 4, 4); $data = substr($buffer, 8); } else if ($len === 127) { $masks = substr($buffer, 10, 4); $data = substr($buffer, 14); } else { $masks = substr($buffer, 2, 4); $data = substr($buffer, 6); } for ($index = 0; $index < strlen($data); $index++) { $decoded .= $data[$index] ^ $masks[$index % 4]; } return $decoded; } /** * 发送消息 * * @param $client * @param $msg */ function send($client, $msg) { $this->say("> " . $msg); $msg = $this->frame($msg); socket_write($client, $msg, strlen($msg)); $this->say("! " . strlen($msg)); } /** * 数据帧 * * @param $s * @return string */ function frame($s) { $a = str_split($s, 125); if (count($a) == 1) { return "\x81" . chr(strlen($a[0])) . $a[0]; } $ns = ""; foreach ($a as $o) { $ns .= "\x81" . chr(strlen($o)) . $o; } return $ns; } /** * 标准输出 * * @param string $msg */ public function say($msg = "") { echo $msg . PHP_EOL; } /** * 异常错误输出 */ public function error() { $error = socket_last_error(); $error_msg = socket_strerror($error); echo $error_msg . PHP_EOL; } /** * 普通错误输出 * * @param string $notice */ public function notice($notice = "") { echo $notice . PHP_EOL; } } new Socket('127.0.0.1', 9777);以上开多窗口可能有问题,下面这个没有问题https://www.cnblogs.com/jiangzuo/p/5896301.html
2023年08月10日
13 阅读
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 点赞
2023-08-10
关于 php json接口开发的注意问题
关于 php json接口开发的注意问题一是注意跨域问题.需要加 Access-Control-Allow-Origin:* http头.(针对于前端浏览器脚本调用接口)二是如果请求的header里 Content-Type: 是 application/json,则需要用 file_get_contents("php://input");接收.如果用 swoole 框架的话,需要用$request->rawContent()接收.如果请求header里Contente-Type是 multipart/form-data,或application/x-www-form-urlencoded或application/octet-stream则需要用 $_POST($_GET)或$_FILES来接收.Ps :js 代码调用接口示例如下1.要加contentType: "application/json; charset=utf-8",2.需要使用JSON.stringify 转换json对象或把对象转为字符形式,如'{"aa":22}'(json两边加单引号)var submit_sync = function() {$.<a class="wpal-linked-keyword" href="https://ajax.p2hp.com/" target="_blank">ajax</a>({ type: "post", url: 'add-post-json.php', async: false, // 使用同步方式 // 1 需要使用JSON.stringify 否则格式为 a=2&b=3&now=14... // 2 需要强制类型转换,否则格式为 {"a":"2","b":"3"} data: JSON.stringify({ a: parseInt($('input[name="a"]').val()), b: parseInt($('input[name="b"]').val()), now: new Date().getTime() // 注意不要在此行增加逗号 }), contentType: "application/json; charset=utf-8", dataType: "json", success: function(data) { $('#result').text(data.result); } // 注意不要在此行增加逗号 }); }PHP请求接口的代码如下:<?phpfunction curl_post($url, $data, $json=true){ $ch = curl_init(); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_URL, $url); if ($json && is_array($data)) { $data = json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_NOSIGNAL, 1); //注意,毫秒超时一定要设置这个 curl_setopt($ch, CURLOPT_TIMEOUT_MS, 2500); //超时毫秒,cURL 7.16.2中被加入。从PHP 5.2.3起可使用if ($json) {curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8','Content-Length:'.strlen($data)));} $relt = curl_exec($ch); var_dump($relt); curl_close($ch); return $relt;}$data=array('key'=>'value');curl_post('http://192.168.6.76:9523/?op=test', $data, true);开发中一般不要用前端代码去调用非本系统的接口地址.参考 https://blog.csdn.net/wangjun5159/article/details/47781443https://blog.csdn.net/wangjun5159/article/details/49644507
2023年08月10日
11 阅读
0 评论
0 点赞
2023-08-10
Swoole 中 Process
Swoole 中 Process这篇 blog 折腾了很久才写出来, 问题主要还是在 理解 上. 有时候就是这样,理解了之后就很简单, 不理解就很难; 知道了就很简单, 不知道往往就很难. 所以 stay hungry stay foolish stay young 真的很重要本来计划开发 swoft 框架 中的 Process 模块, 所以需要对 swoole 的 Process 模块要有比较深入的了解才行. 不过根据 swoole 官方 wiki 的实践过程中, 一直有未理解的部分. 之前虽然也做过多次 多进程编程, 但是当真正需要进行框架开发的时候, 就会发现以前学到的知识不够全面, 无法指导整体的设计. 好在一直在坚持, 奉上现在理解的程度.内容一览:进程相关基础操作: fork/exit/kill/wait进程相关高级操作: 主进程退出子进程干完活后也退出; 子进程异常退出主进程自动重启进程间通信(IPC) - 管道(pipe)进程间通信(IPC) - 消息队列(message queue)swoole process 模块提供的更多功能进程相关基础操作进程是什么: 进程是运行者的程序先来看看一个最简单的例子:<?phpecho posix_getpid(); // 获取当前进程的 pidswoole_set_process_name('swoole process master'); // 修改所在进程的进程名sleep(100); // 模拟一个持续运行 100s 的程序, 这样就可以在进程中查看到它, 而不是运行完了就结束作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。通过 ps aux 查看进程:未设置进程名设置进程名再来看看 swoole 中使用子进程的基础操作:use Swoole\Process;$process = new Process(function (Process $worker) {if (Process::kill($worker->pid, 0)) { // kill操作常用来杀死进程, 传入 0 可以用来检测进程是否存在 $worker->exit(); // 退出子进程 }});$process->start(); // 启动子进程Process::wait(); // 回收退出的子进程作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。new Process(): 通过回调函数来设置子进程将要执行的逻辑$process->start(): 调用 fork() 系统调用, 来生成子进程Process::kill(): kill操作给进程发送信号, 常用来杀死进程, 传入 0 可以用来检测进程是否存在Process::wait(): 调用 wait() 系统调用, 回收子进程, 如果不回收, 子进程会编程 僵尸进程, 浪费系统资源$worker->exit(): 子进程主动退出我在这里有一个疑问:主进程的生命周期是怎么样的? 子进程的生命周期是怎么样的?有这样一个疑问也来自于我之前的思维惯性: 理解一个事物时从事物的生命周期进行理解. 结合 进程是运行着的程序 来一起理解:new Process(): 只有回调函数的逻辑会在进程中执行除此之外的代码都是在主进程中执行进程相关高级操作主进程退出子进程干完活后也退出子进程异常退出主进程自动重启<?phpuse Swoole\Process;class MyProcess1{public $mpid = 0; // master pid, 即当前程序的进程ID public $works = []; // 记录子进程的 pid public $maxProcessNum = 1; public $newIndex = 0; public function __construct() { try { swoole_set_process_name(__CLASS__. ' : master'); $this->mpid = posix_getpid(); $this->run(); $this->processWait(); } catch (\Exception $e) { die('Error: '. $e->getMessage()); } } public function run() { for ($i=0; $i<$this->maxProcessNum; $i++) { $this->createProcess(); } } public function createProcess($index = null) { if (is_null($index)) { $index = $this->newIndex; $this->newIndex++; } $process = new Process(function (Process $worker) use($index) { // 子进程创建后需要执行的函数 swoole_set_process_name(__CLASS__. ": worker $index"); for ($j=0; $j<3; $j++) { // 模拟子进程执行耗时任务 $this->checkMpid($worker); echo "msg: {$j}\n"; sleep(1); } }, false, false); // 不重定向输入输出; 不使用管道 $pid = $process->start(); $this->works[$index] = $pid; return $pid; } // 主进程异常退出, 子进程工作完后退出 public function checkMpid(Process $worker) // demo中使用的引用, 引用表示传的参数可以被改变, 由于传入 $worker 是 \Swoole\Process 对象, 所以不用使用 & { if (!Process::kill($this->mpid, 0)) { // 0 可以用来检测进程是否存在 $worker->exit(); $msg = "master process exited, worker {$worker->pid} also quit\n"; // 需要写入到日志中 file_put_contents('process.log', $msg, FILE_APPEND); // todo: 这句话没有执行 } } // 重启子进程 public function rebootProcess($pid) { $index = array_search($pid, $this->works); if ($index !== false) { $newPid = $this->createProcess($index); echo "rebootProcess: {$index}={$pid}->{$newPid} Done\n"; return; } throw new \Exception("rebootProcess error: no pid {$pid}"); } // 自动重启子进程 public function processWait() { while (1) { if (count($this->works)) { $ret = Process::wait(); // 子进程退出 if ($ret) { $this->rebootProcess($ret['pid']); } } else { break; } } }}new MyProcess1();作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。说明以下几点:子进程运行结束后就会退出, 通过 Process::wait() 检测到子进程退出信号执行自动重启, 子进程就会一直执行下去关于函数参数传 引用/指针, 一个很好的理解方式是: 参数可以被修改运行并模拟主进程异常退出:模拟主进程异常退出输出进程间通信(IPC) - 管道(pipe)管道的几个关键词:半双工: 数据单向流动, 一端只读, 一端只写.同步 vs 异步: 默认为同步阻塞模式, 可以使用 swoole_event_add() 添加管道到 swoole 的 event loop 中, 实现异步IO管道类型(数据格式): SOCK_STREAM, 流式, 需要用户自己处理数据的封包/解包; SOCK_DGRAM, 数据报, 每次收发都是一次完整的数据包 (DGRAM/STREAM)注意, swoole wiki - process->write() 中提到 SOCK_DGRAM 并不会乱序丢包先来看一个简单的例子, php从shell管道中读取数据:// get pip data$fp = fopen('php://stdin', 'r');if ($fp) {while ($line = fgets($fp, 4096)) { echo "php get pip data: ". $line; } fclose($fp);}作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。从shell管道读取数据swoole process中的管道很强大, 支持 子进程写, 主进程读 以及 主进程写, 子进程读:use Swoole\Process;// 子进程写, 父进程读$process = new Process(function (Process $worker) {$worker->write("worker");});$process->start();$msg = $process->read();echo "from process: $msg", "\n";// 父进程写, 子进程读$process = new Process(function (Process $worker) {$msg = $worker->read(); echo "from master: $msg", "\n";});$process->start();$process->write('master');作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。使用管道多次读写注意区分 $worker->write() 和 $process->write(), 之前一直错误的以为这 2 个是相同的, 其实就是把 $process 误以为是子进程, 从而相当于 $process->write() 就是子进程写管道 -- 其实这里是主进程内执行的逻辑, 是主进程写数据到管道, 供子进程读取swoole中其他管道相关操作:异步IOuse Swoole\Process;use Swoole\Event;// 异步IO$process = new Process(function (Process $worker) {$GLOBALS['worker'] = $worker; Event::add($worker->pipe, function (int $pipe) { // 使用 swoole_event_add 添加管道到异步IO /** @var Process $worker */ $worker = $GLOBALS['worker']; $msg = $worker->read(); echo "from master: $msg \n"; $worker->write("hello master"); sleep(2); $worker->exit(0); });});$process->start();$process->write("master msg 1");$msg = $process->read();echo "from process: $msg \n";作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。异步IO设置超时use Swoole\Process;// 设置管道超时$process = new Process(function (Process $worker) {sleep(5);});$process->start();$process->setTimeout(0.5);$ret = $process->read();var_dump($ret);var_dump(swoole_errno());作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。管道超时插播一个趣事, @thinkpc 看完 2017北京PHP开发者年会, 就知道为啥会点赞了关闭管道// 关闭管道: 默认值0->关闭读写 1->关闭写 2->关闭读$process->close();进程间通信(IPC) - 消息队列(message queue)消息队列:一系列保存在内核中的消息链表有一个 msgKey, 可以通过此访问不同的消息队列有数据大小限制, 默认 8192, 可以通过内核修改阻塞 vs 非阻塞: 阻塞模式下 pop()空消息队列/push()满消息队列会阻塞, 非阻塞模式可以直接返回swoole 中使用消息队列:通信模式: 默认为争抢模式, 无法将消息投递给指定子进程新建消息队列后, 主进程就可以使用消息队列不可和管道一起使用, 也无法使用 swoole event loop主进程中要调用 wait(), 否则子进程中调用 pop()/push() 会报错use Swoole\Process;$process = new Process(function (Process $worker) {// $worker->push('worker'); echo "from master: ". $worker->pop(). "\n"; sleep(2); // $worker->exit();}, false, false); // 关闭管道// 参数一为 msgKey, 这里是默认值// 参数二为 通信模式, 默认值 2 表示争抢模式, 这里还加上了 非阻塞$process->useQueue(ftok(__FILE__, 1), 2| Process::IPC_NOWAIT);$process->push('hello1'); // 使用 useQueue 后, 主进程就可以读写消息队列了$process->push('hello2');echo "from woker: ". $process->pop(). "\n";// echo "from woker: ". $process->pop(). "\n";$process->start(); // 启动子进程// 消息队列状态var_dump($process->statQueue());// 删除队列, 如果不调用则不会在程序结束时清楚数据, 下次使用相同 msgKey 时还可以访问数据$process->freeQueue();var_dump(Process::wait()); // 要调用 wait(), 否则子进程中 push()/pop() 会报错作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。消息队列swoole process 模块提供的更多功能swoole_set_process_name(): 修改进程名, 不兼容 macswoole_process->exec(string $execfile, array $args) 执行外部程序参数 $execfile 需要使用可执行文件的绝对路径, 参数 args 为参数数组// 比如 python test.py 123swoole_process->exec('/usr/bin/python', ['test.py', 123]);// 更复杂的例子swoole_process->exec(('/usr/local/bin/php', ['/var/www/project/yii-best-practice/cli/yii', 't/index', '-m=123', 'abc', 'xyz']);// 父进程 exec 进程进行管道通信use Swoole\Process;$process = new Process(function (Process $worker) {$worker->exec('/bin/echo', ['hello']); $worker->write('hello');}, true); // 需要启用标准输入输出重定向$process->start();echo "from exec: ". $process->read(). "\n";作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。父进程与exec进程通过管道通信SwooleProcess::kill($pid, $signo = SIGTERM): 向指定进程发送信号, 默认是终止进程, 传 0 可检测进程是否存在SwooleProcess::wait(): 回收子进程, 如果主进程不调用此方法, 子进程会变成 僵尸进程, 浪费系统资源SwooleProcess::signal(): 异步信号监听use Swoole\Process;// 异步信号监听 + waitProcess::signal(SIGCHLD, function ($signal) { // 监听子进程退出信号// 可能同时有多个子进程退出, 所以要while循环 while ($ret = Process::wait(false)) { // false 表示不阻塞 var_dump($ret); }});作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。SwooleProcess::daemon(): 将当前进程变为一个守护进程use Swoole\Process;// daemonProcess::daemon();swoole_set_process_name('test daemon process');sleep(100);daemon-守护进程SwooleProcess::alarm(): 高精度定时器(微秒级), 对 setitimer 系统调用的封装, 可以配合 SwooleProcess::signal() / pcntl_signal 使用注意不可和 SwooleTimer 同时使用// signal + alarm// 第一个参数表示时间, 单位 us, -1 表示清除定时器// 第二个参数表示类型 0->真实时间->SIGALAM 1->cpu时间->SIGVTALAM 2->用户态+内核态时间->SIGPROFProcess::alarm(100*1000); // 100msProcess::signal(SIGALRM, function ($signal) {static $i = 0; echo "#$i \t alarm \n"; $i++; if ($i>20) { Process::alarm(-1); // -1 表示清除 }});作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。alarmSwooleProcess::setaffinity(): 设置CPU亲和, 即将进程绑定到指定CPU核上传值范围: [0, swoole_cpu_num())CPU亲和: CPU的速度远远高于IO的速度, 所以CPU有多级缓存来解决IO等待的问题, 绑定指定CPU, 更容易命中CPU缓存写在最后资源推荐:图灵社区 - 理解UNIX进程 + 「理解Unix进程」读书笔记blog - 「进程」编程todo:使用输入输出重定向管道类型为 SOCK_STREAM 时的情况, 是否需要 封包/解包 处理, 即 swoole wiki - process->write() 中提到的 管道通信默认的方式是流式,write写入的数据在read可能会被底层合并多进程 + 异步IO 的注意事项能理解 因为子进程会继承父进程的内存和IO句柄 这个会产生的影响, 但是给的示例并没有说明这个问题use Swoole\Process;use Swoole\Event;// 多个子进程 + 异步IO$workers = [];$workerNum = 3;for ($i=0; $i<$workerNum; $i++) {$process = new Process(function (Process $worker) { $worker->write($worker->pid); echo "worker: {$worker->pid} \n"; }); $pid = $process->start(); $workers[$pid] = $process; // Event::add($process->pipe, function (int $pipe) use ($process) { // $data = $process->read(); // echo "recv: $data \n"; // });}foreach ($workers as $worker) {Event::add($worker->pipe, function (int $pipe) use ($worker) { $data = $worker->read(); echo "recv: $data \n"; });}作者:daydaygo链接:https://www.jianshu.com/p/4b6326cdaaa7來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。多进程异步IO始发于简书:daydaygo
2023年08月10日
19 阅读
0 评论
0 点赞
1
...
90
91
92
...
157