Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.
В предыдущем выпуске я рассмотрел в первую очередь безопасность веб-сервера и контроль доступа . В сегодняшнем эпизоде я расскажу о дополнительных мерах защиты, которые я добавил в Meeting Planner. Поскольку весь код написан на Yii2 Framework для PHP, я смог использовать эту среду для ряда этих укреплений. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .
Вы можете попробовать Планировщик собраний прямо сейчас, запланировав свою первую встречу . Не стесняйтесь оставлять отзывы о вашем опыте в комментариях ниже. Я также открыт для новых идей и тематических предложений для будущих уроков.
Здание повышенной безопасности
Реализация различных уровней безопасности для Meeting Planner займет несколько эпизодов. Теперь, когда сервер настроен более надежно, я хочу рассказать вам о других областях безопасности для кода приложения.
Защитные ключи и коды
Очевидно, что важно сохранять аутентификацию ключей вдали от хакеров, но также довольно легко публиковать их в GitHub. Рассказы о случайной регистрации файлов с паролем службы или ключом API.
Чтобы предотвратить это в Yii, я храню внешний файл .ini вне дерева кода. Это загружается в верхней части /frontend/config/main.php и используется для любой необходимой конфигурации компонента:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<?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’],
],
|
В приведенном выше примере вы можете увидеть секреты API Facebook, загруженные из файла инициализации.
Формат файла инициализации довольно прост:
01
02
03
04
05
06
07
08
09
10
11
|
mysql_host=»localhost»
mysql_un=»xxxxxxxxxxxxxxxxxxx»
mysql_db=»xxxxxxxxxxxxxxxxxxx»
mysql_pwd=»xxxxxxxxxxxxxxxxxxx»
mailgun_user = «[email protected]»
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 рекомендует вам разместить некоторые из этих настроек в каталоге / сред, особенно если они различаются между разработкой и производством.
Таким образом, важно, чтобы ваш файл .gitignore исключал локальные версии этих файлов:
1
2
3
4
5
|
#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:
1
2
3
4
5
6
|
<?php
return [
‘ga’ => ‘UA-xxxxxxxxxx-12’,
‘urlPrefix’ => »,
‘google_maps_key’ => ‘AIzzzzzz1111222222xxxxxxQ’,
];
|
Я мог бы потратить еще больше времени на их организацию.
Блокировка плохих регистраций
Для альфа-релиза я разослал обновления волнами. И на ранних этапах Meeting Planner было больше плохих писем, чем я ожидал. Mailgun позволил легко идентифицировать отказов и отказов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
‘[email protected]’, ‘[email protected]’, ‘a@a.
‘[email protected]’, ‘ddd@c.
‘[email protected]’, ‘[email protected]’, ‘mike@mike.
‘[email protected]’, ‘[email protected]’, ‘qweqwe@qwe.
‘[email protected]’, ‘oi.
‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’,
‘[email protected]’, ‘risitesh.
‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’,
‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’,
‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’,
‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’,
‘[email protected]’, ‘endri.
‘[email protected]’, ‘rob.
‘[email protected]’, ‘[email protected]’, ‘ed@ed.
|
Большинство из них, вероятно, из-за разрыва во времени, когда Meeting Planner был новым и бездействующим — во время лечения опухоли головного мозга и операции .
Совсем недавно, добавив социальные учетные записи , я сделал регистрацию на Meeting Planner довольно простой, но регистрация спама все еще возможна. Я хотел, чтобы людям было труднее регистрироваться с плохими электронными письмами.
К счастью, Yii предлагает несколько функций, которые поддерживают это.
Защитный код
Yii2 теперь предлагает встроенную капчу. Таким образом, любой, кто регистрируется с помощью старого метода электронной почты и пароля, должен ввести капчу. Вы можете увидеть поле captcha
ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<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
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
<?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.
[‘password’, ‘required’],
[‘password’, ‘string’, ‘min’ => 6],
[‘captcha’, ‘required’],
[‘captcha’, ‘captcha’],
];
}
|
Если люди не введут правильный ответ с картинки, они не смогут зарегистрироваться. Это затрудняет автоматическую регистрацию спаммеров.
CheckDNS
Я также хотел свести к минимуму регистрацию с поддельным адресом электронной почты. Проверка checkDNS
самом деле ищет действительную запись MX, основанную на домене адреса электронной почты:
1
|
[’email’, ’email’, ‘checkDNS’=>true, ‘enableIDN’=>true],
|
Так, например, если я неправильно набрал gmail.com как gmal.com, checkDNS
вернет false
. Для gmal.com нет зарегистрированной записи MX. Точно так же нет ни одного для spambotolympics9922.com.
В конечном счете, безопасность — это итеративный процесс. Всегда есть чем заняться.
Ограничение оскорбительных действий
Затем я хотел добавить общие ограничения на количество действий, которые могут выполнять люди, чтобы ограничить злоупотребления и не дать приложению стать громоздким.
Создание встречи
Чтобы люди не findEmptyMeeting
создавать множество пустых собраний, я создал findEmptyMeeting
который ищет пустую встречу и использует ее, когда кто-то пытается создать новую:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
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]);
}
|
Другими словами, если пользователь отправляется на создание новой встречи 1700 раз, ему всегда будет представлена первая пустая встреча, которую он создал.
Предельная частота действий
Я также создал обычно структурированный метод withinLimit
для повторного использования вокруг приложения, который может предотвратить слишком много действий за слишком короткое время. В приведенном ниже примере проверяется, что за последний час и последний день было создано не более n заседаний:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
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
чтобы узнать, могут ли они. Если нет, мы показываем сообщение об ошибке:
1
2
3
4
5
6
|
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
, чтобы его можно было изменить позже:
1
|
const MEETING_LIMIT = 7;
|
Затем MeetingTime::withinLimit()
проверяет, чтобы любой пользователь предлагал не более семи раз:
01
02
03
04
05
06
07
08
09
10
11
|
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
, метод создания контроллера проверяет пределы:
1
2
3
4
5
6
|
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']
безопасно использовать для безопасности — другими словами, я читал, что его нельзя подделать.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// 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, eg 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;
}
|
Для собственного тестирования я также разрешаю вошедшему в систему администратору запускать задания cron.
В конце концов, я также могу добавить пароль в мои задания cron и переместить их в операции командной строки.
Заглядывая вперед
За последние два эпизода я выполнил множество улучшений безопасности, но еще многое предстоит сделать. В моем списке есть более глубокий обзор безопасности доступа, особенно через AJAX, отслеживание и блокировка IP-адресов, а также тщательная фильтрация всего пользовательского ввода.
Опять чего вы ждете? Запланируйте свою первую встречу и поделитесь своим мнением в комментариях. Буду также признателен за ваши комментарии по вопросам безопасности.
Как всегда, вы можете посмотреть предстоящие уроки в серии « Построение стартапа с помощью PHP» или подписаться на меня @reifman . Есть еще несколько важных функций.