本教程是 Envato Tuts+ 上的“使用 PHP 构建您的初创公司”系列的一部分。在本系列中,我将使用我的会议规划器应用作为现实生活中的示例,指导您从概念到现实启动一家初创公司。在此过程中的每一步,我都会将 Meeting Planner 代码作为开源示例发布,您可以从中学习。我还将解决出现的与初创企业相关的业务问题。
在上一集中,我主要介绍了 Web 服务器安全和访问控制。在今天的节目中,我将讨论我添加到 Meeting Planner 中的其他保护措施。由于所有代码都是在 PHP 的 Yii2 框架中编写的,因此我能够利用该框架来进行许多防御工事。如果您想了解有关 Yii2 的更多信息,请查看我们的并行系列“使用 Yii2 进行编程”。
您现在可以通过安排第一次会议来试用会议规划器。请随时在下面的评论中发表有关您的体验的反馈。我也愿意接受新功能的想法和未来教程的主题建议。
增强安全性
为 Meeting Planner 实施不同级别的安全性需要花费数个时间。现在服务器的配置更加稳健,我想指导您了解应用程序代码的其他安全领域。
保护密钥和代码
显然,让身份验证密钥远离黑客很重要,但将它们发布到 GitHub 也很容易。有报道称,使用服务密码或 API 密钥意外签入文件。
为了防止在 Yii 中出现这种情况,我在代码树之外保留了一个外部 .ini 文件。这会在 /frontend/config/main.php 的顶部加载,并用于任何必要的组件配置:
'<?php
$config = parse_ini_file('/var/secure/meetme.ini', true);
$params = array_merge(
require(__DIR__ . '/../../common/config/params.php'),
require(__DIR__ . '/../../common/config/params-local.php'),
require(__DIR__ . '/params.php'),
require(__DIR__ . '/params-local.php')
);
return [
'id' => 'mp-frontend',
'name' => 'Meeting Planner',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'controllerNamespace' => 'frontendcontrollers',
'components' => [
'authClientCollection' => [
'class' => 'yiiauthclientCollection',
'clients' => [
'facebook' => [
'class' => 'yiiauthclientclientsFacebook',
'clientId' => $config['oauth_fb_id'],
'clientSecret' => $config['oauth_fb_secret'],
],
在上面的示例中,您可以看到从初始化文件加载的 Facebook API 机密。
初始化文件的格式相当简单:
'mysql_host="localhost"
mysql_un="xxxxxxxxxxxxxxxxxxx"
mysql_db="xxxxxxxxxxxxxxxxxxx"
mysql_pwd="xxxxxxxxxxxxxxxxxxx"
mailgun_user = "xxxxxxxxxxxxxxxxxxx@meetingplanner.io"
mailgun_pwd = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
mailgun_api_key="key-9p-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
mailgun_api_url="https://api.mailgun.net/v2"
mailgun_public_key="pubkey-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
oauth_fb_id="1xxxxxxxxxxxxxxxxxxx3"
oauth_fb_secret="bcxxxxxxxxxxxxxxxxxxxda"
Yii2 鼓励您将其中一些设置放在 /environments 目录中,特别是当开发和生产之间的设置有所不同时。
因此,您的 .gitignore 文件排除这些文件的本地版本非常重要:
'#local environment files
/environments/prod/common/config/main-local.php
/environments/prod/frontend/config/main-local.php
/frontend/config/params-local.php
/frontend/config/main-local.php
这是我的本地参数文件之一的示例,/frontend/config/params-local.php:
'<?php
return [
'ga' => 'UA-xxxxxxxxxx-12',
'urlPrefix' => '',
'google_maps_key' => 'AIzzzzzz1111222222xxxxxxQ',
];
我可能可以花更多时间更好地组织这些。
阻止不良注册
对于 Alpha 版本,我分批发送了更新。而且,在 Meeting Planner 的早期阶段,不良电子邮件的数量比我预期的要多。 Mailgun 可以轻松识别退回邮件和失败:
'$badEmails=[ '', 'test2@gmail.com', '1111@gmail.com', 'qwerty@gmail.com',
'amjadiqbalkhanniazi@gmail.com', 'admin@admin.com', 'rhizalpatra@fellow.lpkia.ac.id', 'tm@archi.com',
'test@test.com', 'web@yahoo.fr', 'a@a. a', 'ailaa@aa.com', 'be@yahoo.fr', 'vico@gmail.com',
'nobu@gmail.com', 'a@gmail.com', 'ct@gmail.com', 'sanjaydk@projectdemo. biz', 'trial@gmail.com',
'varlog255q@hotmail.com', 'baah@baah.com', 'minhvnn1@gmail.com', 'test@gmail.com',
'test@mediabite.co.uk', 'ddd@c. hu', 'ddd@ymail.com', 'a. chetan@saisoftex.com', 'user02@local.com',
'Imrky4@gmail.com', 'robomadybu@hotmail.com', 'mike@mike. mike', 'abcd@gmail.com',
'azazaz@azazaza.com', 'mama@mama.mn', 'qweqwe@qwe. qwe', 'testere@wp.pl', 'kaze@hotmail.com',
'test@usertest.fr', 'demodemo@demo.com', 'qqq@dd.gh', 'gnfbb@h. vo', 'admin@admin123.com',
'testsir@testsir.com', 'oi. hd@yeah1.vn', 'loi. hd@yeah1.vn', 'test@email.com', 'salom@salom.com',
'ar@yahoo.com', 'lex@gmail.com', 'Tester1234@gmail.com', 'mantaf@mail.com', 'aaa@aaa.com',
'oeui@gmail.com', 'risitesh. biswal14@yahoo.com', 'ttt@wp.pl', 'nnn@nnn.net', 'nnn2@nnn.net',
'ana@gmail.com', 'asdf@yahoo.com', 'noom@gmail.com', 'jomon@example.com', 'asdfasdf@yahoo.com',
'admin@yahoo.com', 'abinubli@mail.com', 'tes@tes.com', 'asdasdr@asd.com', 'something@some.com',
'ademin@example.com', 'd@dd.com', 'robo@gmail.com', 'toto@titi.com', 'fesfe@fseff. fes',
'master@wpthemeslist.com', 'teste@teste.com', 'barny182@hotmail.com', 'test@admin.com',
'billtian@test.com', 'Test@goggle.ca', 'jm@gmail.com', 'john-panin@qip.ru', 'loslos@loslos.com',
'ghfhf@jhgjgjk.com', 'lol@lol.com', 'tester1@gmail.com', 'g0952180828@gmail.com', 'testim@testim.com',
'mnml.name@gmail.com', 'endri. azizi. 92@gmail.com', '123123@gmail.com', 'myfriend@gmai.com',
'geraldo_1989@hotmail.com', 'rob. test. 999@gmail.com', 'j@c. com', 'Agung. andika@mhs.uinjkt.ac.id',
'W3test@ya.ru', 'user@ya.ru', 'ed@ed. fl', 'ed@ed.es', ];
其中大部分可能是在会议计划器刚推出时闲置的时候出现的——在我的脑肿瘤治疗和手术期间。
最近,通过添加社交登录,我使注册 Meeting Planner 变得非常容易,但垃圾邮件注册仍然是可能的。我想让人们更难使用不良电子邮件进行注册。
幸运的是,Yii 提供了一些支持此功能的功能。
验证码
Yii2 现在提供内置验证码。因此,任何使用旧式电子邮件和密码方法注册的人都必须输入验证码。您可以在下面看到 captcha
字段:
<p>Or, fill out the following fields to register manually:</p>
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'form-signup']); ?>
<?= $form->field($model, 'username') ?>
<?=
$form->field($model, 'email', ['errorOptions' => ['class' => 'help-block' ,'encode' => false]])->textInput() ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= $form->field($model, 'captcha')->widget(yiicaptchaCaptcha::classname(), [
// configure additional widget properties here
]) ?>
<div class="form-group">
<?= Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
然后,将验证码合规性添加为 SignupForm
模型的规则:
<?php
namespace frontendmodels;
use commonmodelsUser;
use yiibaseModel;
use Yii;
use yiihelpersHtml;
use yiivalidatorsEmailValidator;
/**
* Signup form
*/
class SignupForm extends Model
{
public $username;
public $email;
public $password;
public $captcha;
/**
* @inheritdoc
*/
public function rules()
{
return [
['username', 'filter', 'filter' => 'trim'],
['username', 'required'],
['username', 'unique', 'targetClass' => 'commonmodelsUser', 'message' => 'This username has already been taken.'],
['username', 'string', 'min' => 2, 'max' => 255],
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email', 'checkDNS'=>true, 'enableIDN'=>true],
['email', 'unique', 'targetClass' => 'commonmodelsUser', 'message' => 'This email address has already been taken. '.Html::a('Looking for your password?', ['site/request-password-reset'])],
['password', 'required'],
['password', 'string', 'min' => 6],
['captcha', 'required'],
['captcha', 'captcha'],
];
}
如果人们没有输入正确的验证码响应,他们就无法注册。这使得垃圾邮件发送者很难进行自动注册。
检查DNS
我还想尽量减少使用虚假电子邮件地址进行注册的情况。 Yii 的 checkDNS
验证实际上会根据电子邮件地址的域查找有效的 MX 记录:
['email', 'email', 'checkDNS'=>true, 'enableIDN'=>true],
例如,如果我将 gmail.com 误输入为 gmal.com,则 checkDNS
将返回 false
。 gmal.com 没有注册 MX 记录。同样,spambotolympics9922.com 也没有。
最终,安全性是一个迭代过程。总是有更多事情要做。
限制滥用行为
接下来,我想为人们可以执行的操作数量添加常见限制,以限制滥用并防止应用程序变得笨拙。
会议创建
为了防止人们创建大量空会议,我创建了一个 findEmptyMeeting
,它会查找空会议并在有人尝试创建新会议时重用它:
public function actionCreate()
{
// prevent creation of numerous empty meetings
$meeting_id = Meeting::findEmptyMeeting(Yii::$app->user->getId());
//echo $meeting_id;exit;
if ($meeting_id===false) {
// otherwise, create a new meeting
$model = new Meeting();
$model->owner_id= Yii::$app->user->getId();
$model->sequence_id = 0;
$model->meeting_type = 0;
$model->save();
$model->initializeMeetingSetting($model->id,$model->owner_id);
$meeting_id = $model->id;
}
$this->redirect(['view', 'id' => $meeting_id]);
}
换句话说,如果用户创建新会议 1,700 次,他们将始终看到他们创建的第一个空会议。
限制操作频率
我还创建了一个通用结构的 withinLimit
方法,以便在应用程序周围重用,这可以防止在短时间内执行过多操作。下面的示例检查过去一小时和最后一天内创建的会议数量是否不超过 n:
public static function withinLimit($user_id,$minutes_ago = 180) {
// how many meetings created by this user in past $minutes_ago
$cnt = Meeting::find()
->where(['owner_id'=>$user_id])
->andWhere('created_at>'.(time()-($minutes_ago*60)))
->count();
if ($cnt >= Meeting::NEAR_LIMIT ) {
return false;
}
// check in last DAY_LIMIT
$cnt = Meeting::find()
->where(['owner_id'=>$user_id])
->andWhere('created_at>'.(time()-(24*3600)))
->count();
if ($cnt >= Meeting::DAY_LIMIT ) {
return false;
}
return true;
}
每当有人尝试创建会议时,我们都会检查 withinLimit
以查看他们是否可以。如果没有,我们会显示 flash
错误消息:
public function actionCreate()
{
if (!Meeting::withinLimit(Yii::$app->user->getId())) {
Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, there are limits on how quickly you can create meetings. Visit support if you need assistance.'));
return $this->redirect(['index']);
}
限制操作数量
我还想限制操作的总数。例如,每个会议参与者只能为每次会议添加七个会议日期时间。在 MeetingTime.php 中,我设置了 MEETING_LIMIT
,以便稍后可以更改:
const MEETING_LIMIT = 7;
然后,MeetingTime::withinLimit()
检查以确保任何用户的建议次数不超过七次:
public static function withinLimit($meeting_id) {
// how many meetingtimes added to this meeting
$cnt = MeetingTime::find()
->where(['meeting_id'=>$meeting_id])
->count();
// per user limit option: ->where(['suggested_by'=>$user_id])
if ($cnt >= MeetingTime::MEETING_LIMIT ) {
return false;
}
return true;
}
当他们去创建 MeetingTime
时,控制器创建方法会检查限制:
public function actionCreate($meeting_id)
{
if (!MeetingTime::withinLimit($meeting_id)) {
Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you have reached the maximum number of date times per meeting. Contact support if you need additional help or want to offer feedback.'));
return $this->redirect(['/meeting/view', 'id' => $meeting_id]);
}
保护 CRON 作业
今天最后,我想确保对远程 cron 作业的访问安全。互联网上描述了一些有趣的方法。目前,我正在检查 $_SERVER['REMOTE_ADDR']
(请求 IP 地址)是否与托管 $_SERVER['SERVER_ADDR' 是同一服务器]
,本地IP地址。 $_SERVER['REMOTE_ADDR']
可以安全使用,换句话说,我已经了解到它无法被欺骗。
// only cron jobs and admins can run this controller's actions
public function beforeAction($action)
{
// your custom code here, if you want the code to run before action filters,
// which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl
if (!parent::beforeAction($action)) {
return false;
}
// other custom code here
if (( $_SERVER['REMOTE_ADDR'] == $_SERVER['SERVER_ADDR'] ) ||
(!Yii::$app->user->isGuest && commonmodelsUser::findOne(Yii::$app->user->getId())->isAdmin()))
{
return true;
}
return false; // or false to not run the action
}
对于我自己的测试,我还允许登录的管理员运行 cron 作业。
最终,我还可以为我的 cron 作业添加密码,并将它们移至命令行操作。
展望未来
在过去的两集中,我已经完成了许多安全改进,但仍有更多工作要做。我的候选清单上包括对访问安全性的更深入审查,特别是通过 AJAX、IP 地址跟踪和阻止,以及仔细过滤所有用户输入。
再说一遍,你还在等什么?安排您的第一次会议,并在评论中分享您的反馈。我也非常感谢您对安全问题的评论。
与往常一样,您可以观看“使用 PHP 构建您的初创公司”系列中即将推出的教程,或关注我 @reifman。还有更多重要功能即将推出。
相关链接
- 会议策划
- 关注会议策划者的资金概况
- 建立您的初创公司:基本安全性 (Envato Tuts+)
- 使用 Yii2 编程:安全性 (Envato Tuts+)
- Yii2 开发者交流会