本教程是 Envato Tuts +上的“ 使用PHP构建启动”系列 的一部分。 在本系列文章中,我 将以 我的 Meeting Planner 应用程序作为真实示例 ,指导您完成从概念到现实的启动 。 在此过程的每一步中,我都会将Meeting Planner代码作为开放源代码示例发布,您可以从中学习。 我还将解决与启动相关的业务问题。
在上一集中,我主要介绍了Web服务器的安全性和访问控制 。 在今天的一集中,我将讨论我添加到Meeting Planner中的其他保护措施。 由于所有代码都是在PHP的Yii2框架中编写的,因此我能够利用该框架进行许多这些防御工事。 如果您想了解有关Yii2的更多信息,请查看我们的平行系列“ 使用Yii2编程” 。
您可以立即安排第一次会议,以试用Meeting Planner。 请在下面的评论中随意发表有关您的经历的反馈。 我也乐于接受新功能的想法和主题建议,以供将来的教程使用。
建立增强的安全性
为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' => 'frontend\controllers',
'components' => [
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'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刚好闲着闲着的时间。
最近,通过添加社交登录名 ,我已经很轻松地注册了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(\yii\captcha\Captcha::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 frontend\models;
use common\models\User;
use yii\base\Model;
use Yii;
use yii\helpers\Html;
use yii\validators\EmailValidator;
/**
* 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' => '\common\models\User', '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' => '\common\models\User', '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
有人尝试创建会议时,我们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()
检查以确保任何用户建议的提议次数都不超过7次:
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
,控制器的create方法检查限制:
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 && \common\models\User::findOne(Yii::$app->user->getId())->isAdmin()))
{
return true;
}
return false; // or false to not run the action
}
对于我自己的测试,我还允许登录的管理员运行cron作业。
最终,我可能还会在我的cron作业中添加一个密码,并将其移至命令行操作。
展望未来
在过去的两集中,我已经完成了许多安全性改进,但是还有更多工作要做。 在我的名单上,是对访问安全性的更深入的审查,尤其是通过AJAX,IP地址跟踪和阻止,以及仔细过滤所有用户输入的访问安全性。
同样,您还在等什么? 安排您的第一次会议 ,并在评论中分享您的反馈。 我也很感谢您对安全问题的评论。
与往常一样,您可以在“ 用PHP构建启动”系列中观看即将举行的教程,或者关注我@reifman 。 还有其他一些重要功能。
相关链接
翻译自: https://code.tutsplus.com/tutorials/building-your-startup-increasing-security--cms-26703