简介
PHP是一门复杂的语言,经过多年折腾,使其不同版本之间高度不一致,有时还有些bug。每个版本都有自己独有的特性、多余和怪异之处,也很难跟踪哪个版本有哪些问题。这也就很好理解为什么有时它会遭到那么多的厌恶。
尽管如此,如今它还是Web开发方面最流行的语言。因其悠久的历史,对于实现密码哈希和数据库访问诸如此类的基本任务你能够找到很多教程。但问题在于,5个教程,你就很有可能找到5种完全不同的完成任务的方式,那么哪种是「正确」的方式呢?其他方式有难以捉摸的bug或者陷阱?确实很难搞明白,所以你经常要在互联网上反复查找尝试确认正确的答案。
这也是PHP编程新手频繁地因为丑陋、过时、或不安全的代码而遭到责备的原因之一。如果Google搜索的第一个结果是一篇年前的文章,讲述一种5年前的方法,那么PHP新手们也就很难改变经常遭受责备的现状。
本文档通过为PHP中常见的令人困惑的问题和任务编辑组织一系列被认为最佳实践的基本做法,来尝试解决上述问题。若一个低层次的任务在PHP中有多种令人困惑的实现方式,本文也会涵盖。
是什么
这是一份指南,在PHP程序员遇到一些常见低层次任务但不明确最佳做法(由于PHP可能提供了多种解决方案)之时,为其建议最佳实践。例如:连接数据库是一个常见任务,PHP中提供了大量可行的方案,但并不是所有的都是好的做法,因此,本文也会包含该问题。
本文包含的是一系列简短的、入门性质的方案。涉及的示例在基本设定下就能够运行起来,你研究一下应该就能把它们变为对你有用的东西。
本文将指出一些我们认为是PHP中最新最好的东西。然而,这意味如果你在使用老版本的PHP,一些用来实现这些解决方案的特性对你并不可用。
这份文档会一直更新,我会尽我最大努力保持该文档与PHP的发展同步。
不是什么
本文档不是一份PHP教程。你应该在别处学习语言基础和语法。
它也不是一份针对web应用常见问题,如cookie存储、缓存、编程风格、文档等的指南。
它也不是一个安全指南。当本文档触碰到一些安全相关的问题时,也是希望你自己做些研究来确保你的PHP应用的安全问题。你的代码造成的问题应该都是自己的过错。
该文档也并不是在主张一种特定的编程风格、模式或者框架。
也不是在主张一种特定的方式来完成高层次任务如用户注册、登录系统等。本文档只限于PHP的悠久历史所造成的一些易混淆或不明确的低层次任务。
它不是一个一劳永逸的解决方案,也不是一个唯一的方案。下面要讲述的一些方法对于你的特定场景来说也许并不是最好的,存在很多不同的方式来达到同样的目的。特别是,高负载web应用也许能从更加难懂的方案中获益更多。
我们在使用哪个版本的PHP?
带Suhosin-Patch的PHP5..10-1ubuntu.6,安装在Ubuntu1.0LTS上。
PHP是Web世界里的百年老龟,它的壳上铭刻着一段丰富、复杂、而粗糙的历史。在一个共享主机的环境里,它的配置可能会限制你能做的事情。
为了保持清晰地叙述,我们将仅针对一个版本的PHP进行讲述。在01年月0日时,该版本为PHP5..10-1ubuntu.6withSuhosin-Patch。若你在Ubuntu1.0LTS服务器上使用apt-get进行安装的就是该版本的PHP。
你也许发现这些方案中的一些在其他或者更老版本的PHP上也能工作。如果是这样的话,就由你来研究在这些更老版本上潜在的难以捉摸的bug或安全问题。
存储密码
使用phpass库来哈希和比较密码
经phpass0.测试,在存入数据库之前进行哈希保护用户密码的标准方式。许多常用的哈希算法如md5,甚至是sha1对于密码存储都是不安全的,因为骇客能够使用那些算法轻而易举地破解密码。
对密码进行哈希最安全的方法是使用bcrypt算法。开源的phpass库以一个易于使用的类来提供该功能。
示例
?php//Includephpass库qui_once(phpass-0/PasswordHash.php)//初始化散列器为不可移植(这样更安全)$hasher=newPasswordHash(8,false);//计算密码的哈希值。$hashedPassword是一个长度为60个字符的字符串.$hashedPassword=$hasher-HashPassword(mysupercoolpassword);//你现在可以安全地将$hashedPassword保存到数据库中!//通过比较用户输入内容(产生的哈希值)和我们之前计算出的哈希值,来判断用户是否输入了正确的密码$hasher-CheckPassword(thewrongpassword,$hashedPassword);//false$hasher-CheckPassword(mysupercoolpassword,$hashedPassword);//true?
陷阱
许多资源可能推荐你在哈希之前对你的密码“加盐”。想法很好,但phpass在HashPassword()函数中已经对你的密码“加盐”了,这意味着你不需要自己“加盐”。
进一步阅读
phpass为什么使用md5或sha哈希密码是不安全的(中文)怎样安全地存储密码
PHP与MySQL
使用PDO及其预处理语句功能。
在PHP中,有很多方式来连接到一个MySQL数据库。PDO(PHP数据对象)是其中最新且最健壮的一种。PDO跨多种不同类型数据库有一个一致的接口,使用面向对象的方式,支持更多的新数据库支持的特性。
你应该使用PDO的预处理语句函数来帮助防范SQL注入攻击。使用函数bindValue来确保你的SQL免于一级SQL注入攻击。(虽然并不是%安全的,查看进一步阅读获取更多细节。)在以前,这必须使用一些「魔术引号(magicquotes)」函数的组合来实现。PDO使得那堆东西不再需要。
示例
?phptry{//新建一个数据库连接//Youllprobablywanttoplacehostnamewithlocalhostinthefirstparameter.//ThePDOoptionswepassdothefollowing://\PDO::ATTR_ERRMODEenablesexceptionsforerrors.Thisisoptionalbutcanbehandy.//\PDO::ATTR_PERSISTENTdisablespersistentconnections,whichcancauseconcurncyissuesincertaincases.See"Gotchas".//\PDO::MYSQL_ATTR_INIT_COMMANDalertstheconnectionthatwellbepassingUTF-8data.//Thismaynotbequiddependingonyourconfiguration,butitllsaveyouheadachesdowntheroad//ifyoutryingtostoUnicodestringsinyourdatabase.See"Gotchas".$link=new\PDO(mysql:host=your-hostname;dbname=your-db,your-username,your-password,array(\PDO::ATTR_ERRMODE=\PDO::ERRMODE_EXCEPTION,\PDO::ATTR_PERSISTENT=false,\PDO::MYSQL_ATTR_INIT_COMMAND=setnamesutf8mb));$handle=$link-ppa(selectUsernamefromUserswheUserId=?orUsername=?limit?);//PHPbug:ifyoudontspecifyPDO::PARAM_INT,PDOmayenclosetheargumentinquotes.//ThiscanmessupsomeMySQLqueriesthatdontexpectintegerstobequoted.//See: