Ce tutoriel fait partie de la série « Construisez votre startup avec PHP » sur Envato Tuts+. Dans cette série, je vais vous guider tout au long du lancement d'une startup, du concept à la réalité, en utilisant mon application de planification de réunions comme exemple concret. À chaque étape du processus, je publierai le code de Meeting Planner comme exemple open source dont vous pourrez tirer des leçons. J'aborderai également les problèmes commerciaux liés aux startups qui se posent.
Dans le dernier épisode, j'ai principalement présenté la sécurité des serveurs Web et le contrôle d'accès. Dans l'émission d'aujourd'hui, je discuterai des protections supplémentaires que j'ai ajoutées à Meeting Planner. Étant donné que tout le code a été écrit dans le framework PHP Yii2, j'ai pu exploiter le framework pour de nombreuses fortifications. Si vous souhaitez en savoir plus sur Yii2, consultez notre série parallèle « Programmation avec Yii2 ».
Vous pouvez essayer le planificateur de réunions dès maintenant en planifiant votre première réunion. N'hésitez pas à poster des commentaires sur votre expérience dans les commentaires ci-dessous. Je suis également ouvert aux idées de nouvelles fonctionnalités et aux suggestions de sujets pour les futurs didacticiels.
La mise en œuvre de différents niveaux de sécurité pour Meeting Planner prend plusieurs heures. Maintenant que le serveur est configuré de manière plus robuste, je souhaite vous guider à travers d'autres domaines de sécurité de votre code d'application.
Évidemment, il est important de garder vos clés d’authentification à l’abri des pirates, mais il est également facile de les publier sur GitHub. Des rapports font état de fichiers archivés accidentellement à l'aide de mots de passe de service ou de clés API.
Pour éviter que cela ne se produise dans Yii, je conserve un fichier .ini externe en dehors de l'arborescence des codes. Celui-ci sera chargé en haut de /frontend/config/main.php et utilisé pour toute configuration de composant nécessaire :
<?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'], ],
Dans l'exemple ci-dessus, vous pouvez voir le secret de l'API Facebook chargé à partir du fichier d'initialisation.
Le format du fichier d'initialisation est assez simple :
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 vous encourage à mettre certains de ces paramètres dans le répertoire /environments, surtout si les paramètres diffèrent entre le développement et la production.
Il est donc important que votre fichier .gitignore exclue les versions locales de ces fichiers :
#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
Voici un exemple d'un de mes fichiers de paramètres locaux, /frontend/config/params-local.php :
<?php return [ 'ga' => 'UA-xxxxxxxxxx-12', 'urlPrefix' => '', 'google_maps_key' => 'AIzzzzzz1111222222xxxxxxQ', ];
Je pourrais probablement passer plus de temps à mieux les organiser.
Pour la version Alpha, j'ai envoyé la mise à jour par lots. De plus, au début de Meeting Planner, il y avait plus de mauvais e-mails que prévu. Mailgun facilite l'identification des rebonds et des échecs :
$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', ];
La majeure partie de cela s'est probablement produite lorsque l'organisateur de réunions était inactif lors de sa première sortie – pendant mon traitement pour une tumeur au cerveau et mon opération chirurgicale.
Récemment, j'ai rendu très simple l'inscription à Meeting Planner en ajoutant la connexion sociale, mais l'enregistrement anti-spam est toujours possible. Je veux qu'il soit plus difficile pour les gens de s'inscrire avec un mauvais e-mail.
Heureusement, Yii fournit certaines fonctionnalités pour prendre en charge cette fonctionnalité.
Yii2 fournit désormais des codes de vérification intégrés. Par conséquent, toute personne s’inscrivant en utilisant l’ancienne méthode d’e-mail et de mot de passe devra saisir un code de vérification. Vous pouvez voir les captcha
champs ci-dessous :
<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>
Ensuite, ajoutez la conformité CAPTCHA comme règle pour le modèle 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'], ]; }
Si les gens ne saisissent pas la bonne réponse captcha, ils ne peuvent pas s’inscrire. Cela rend difficile pour les spammeurs d’automatiser les inscriptions.
Je souhaite également minimiser l'utilisation de fausses adresses e-mail pour m'inscrire. La validation checkDNS
de Yii recherche en fait un enregistrement MX valide en fonction du domaine de l'adresse e-mail :
['email', 'email', 'checkDNS'=>true, 'enableIDN'=>true],
Par exemple, si je tape par erreur gmail.com comme gmal.com, checkDNS
将返回 false
renverra false
. gmal.com n'a aucun enregistrement MX enregistré. De même, spambotolympics9922.com ne le fait pas.
En fin de compte, la sécurité est un processus itératif. Il y a toujours plus à faire.
Ensuite, je souhaite ajouter des limites communes au nombre d'actions que les gens peuvent effectuer pour limiter les abus et empêcher l'application de devenir lourde.
Pour éviter que les gens créent beaucoup de réunions vides, j'ai créé un findEmptyMeeting
qui recherche les réunions vides et les réutilise chaque fois que quelqu'un essaie d'en créer une nouvelle : < /p>
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]); }
En d'autres termes, si un utilisateur crée une nouvelle réunion 1 700 fois, il verra toujours la première réunion vide qu'il crée.
J'ai également créé une withinLimit
méthode d'une structure commune à réutiliser autour de l'application, cela évite que trop d'opérations soient effectuées dans un court laps de temps. L'exemple suivant vérifie si le nombre de réunions créées au cours de la dernière heure et du dernier jour ne dépasse pas 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 作业的访问安全。互联网上描述了一些有趣的方法。目前,我正在检查 $_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。还有更多重要功能即将推出。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!