() translation by (you can also view the original English article)



Tutorial ini adalah bagian dari serial Membangun Startup Anda dengan PHP di Envato Tuts+. Dalam seri ini, saya membimbing Anda melalui peluncuran startup dari konsep ke kenyataan menggunakan aplikasi Meeting Planner saya sebagai contoh di kehidupan nyata. Setiap langkah di sepanjang jalan, saya akan merilis kode Meeting Planner sebagai contoh sumber terbuka yang bisa Anda pelajari. Saya juga akan membahas masalah bisnis terkait startup saat mereka muncul.
Pada episode sebelumnya, saya membahas terutama keamanan server web dan kontrol akses. Dalam episode hari ini, saya akan membahas pengamanan tambahan yang saya tambahkan ke Meeting Planner. Karena semua kode ditulis dalam Yii2 Framework untuk PHP, saya dapat memanfaatkan kerangka untuk sejumlah pertahanan ini. Jika Anda ingin mempelajari lebih lanjut tentang Yii2, lihat seri paralel kami Pemrograman dengan Yii2.
Anda bisa mencoba Meeting Planner sekarang dengan menjadwalkan pertemuan pertama Anda. Jangan ragu untuk mengirim umpan balik tentang pengalaman Anda di komentar di bawah ini. Saya juga terbuka terhadap gagasan dan saran fitur baru untuk tutorial masa depan.
Membangun Peningkatan Keamanan
Menerapkan berbagai tingkat keamanan untuk Meeting Planner akan mengambil beberapa episode. Setelah server dikonfigurasi lebih kuat, saya ingin membimbing Anda melalui area keamanan lainnya untuk kode aplikasi.
Melindungi Kunci dan Kode
Jelas, penting untuk tetap mengautentikasi kunci dari hacker, tapi juga mudah untuk menerbitkannya ke GitHub. Cerita dikisahkan tentang kesalahan check-in dari tidak disengaja oleh file-file dengan kata kunci layanan atau kunci API.
Untuk mencegah hal ini di Yii, saya menyimpan file .ini eksternal di luar pohon kode. Ini akan dimuat di bagian atas dari /frontend/config/main.php dan digunakan untuk konfigurasi komponen apa saja yang diperlukan:
1 |
<?php
|
2 |
$config = parse_ini_file('/var/secure/meetme.ini', true); |
3 |
|
4 |
$params = array_merge( |
5 |
require(__DIR__ . '/../../common/config/params.php'), |
6 |
require(__DIR__ . '/../../common/config/params-local.php'), |
7 |
require(__DIR__ . '/params.php'), |
8 |
require(__DIR__ . '/params-local.php') |
9 |
);
|
10 |
|
11 |
return [ |
12 |
'id' => 'mp-frontend', |
13 |
'name' => 'Meeting Planner', |
14 |
'basePath' => dirname(__DIR__), |
15 |
'bootstrap' => ['log'], |
16 |
'controllerNamespace' => 'frontend\controllers', |
17 |
'components' => [ |
18 |
'authClientCollection' => [ |
19 |
'class' => 'yii\authclient\Collection', |
20 |
'clients' => [ |
21 |
'facebook' => [ |
22 |
'class' => 'yii\authclient\clients\Facebook', |
23 |
'clientId' => $config['oauth_fb_id'], |
24 |
'clientSecret' => $config['oauth_fb_secret'], |
25 |
],
|
Pada contoh di atas, Anda bisa melihat rahasia API Facebook yang dimuat dari file inisialisasi.
Format file inisialisasi cukup mudah:
1 |
mysql_host="localhost" |
2 |
mysql_un="xxxxxxxxxxxxxxxxxxx" |
3 |
mysql_db="xxxxxxxxxxxxxxxxxxx" |
4 |
mysql_pwd="xxxxxxxxxxxxxxxxxxx" |
5 |
mailgun_user = "xxxxxxxxxxxxxxxxxxx@meetingplanner.io" |
6 |
mailgun_pwd = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
7 |
mailgun_api_key="key-9p-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
8 |
mailgun_api_url="https://api.mailgun.net/v2" |
9 |
mailgun_public_key="pubkey-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
10 |
oauth_fb_id="1xxxxxxxxxxxxxxxxxxx3" |
11 |
oauth_fb_secret="bcxxxxxxxxxxxxxxxxxxxda" |
Yii2 mendorong Anda untuk menempatkan beberapa pengaturan ini di direktori /environments, terutama bila pengaturannya bervariasi antara pengembangan dan produksi.
Jadi, penting bahwa file .gitignore Anda mengecualikan versi lokal dari file-file ini:
1 |
#local environment files
|
2 |
/environments/prod/common/config/main-local.php |
3 |
/environments/prod/frontend/config/main-local.php |
4 |
/frontend/config/params-local.php |
5 |
/frontend/config/main-local.php |
Berikut adalah contoh salah satu file parameter lokal saya, /frontend/config/params-local.php:
1 |
<?php
|
2 |
return [ |
3 |
'ga' => 'UA-xxxxxxxxxx-12', |
4 |
'urlPrefix' => '', |
5 |
'google_maps_key' => 'AIzzzzzz1111222222xxxxxxQ', |
6 |
];
|
Saya mungkin bisa menghabiskan lebih banyak waktu untuk mengaturnya dengan lebih baik.
Memblokir Pendaftaran yang Buruk



Untuk rilis alfa, saya mengirimkan update dalam gelombang-gelombang. Dan, pada tahap awal Meeting Planner, ada sejumlah besar email buruk dari yang saya harapkan. Mailgun mempermudah identifikasi bounce dan kegagalan:
1 |
$badEmails=[ '', 'test2@gmail.com', '1111@gmail.com', 'qwerty@gmail.com', |
2 |
'amjadiqbalkhanniazi@gmail.com', 'admin@admin.com', 'rhizalpatra@fellow.lpkia.ac.id', 'tm@archi.com', |
3 |
'test@test.com', 'web@yahoo.fr', 'a@a. a', 'ailaa@aa.com', 'be@yahoo.fr', 'vico@gmail.com', |
4 |
'nobu@gmail.com', 'a@gmail.com', 'ct@gmail.com', 'sanjaydk@projectdemo. biz', 'trial@gmail.com', |
5 |
'varlog255q@hotmail.com', 'baah@baah.com', 'minhvnn1@gmail.com', 'test@gmail.com', |
6 |
'test@mediabite.co.uk', 'ddd@c. hu', 'ddd@ymail.com', 'a. chetan@saisoftex.com', 'user02@local.com', |
7 |
'Imrky4@gmail.com', 'robomadybu@hotmail.com', 'mike@mike. mike', 'abcd@gmail.com', |
8 |
'azazaz@azazaza.com', 'mama@mama.mn', 'qweqwe@qwe. qwe', 'testere@wp.pl', 'kaze@hotmail.com', |
9 |
'test@usertest.fr', 'demodemo@demo.com', 'qqq@dd.gh', 'gnfbb@h. vo', 'admin@admin123.com', |
10 |
'testsir@testsir.com', 'oi. hd@yeah1.vn', 'loi. hd@yeah1.vn', 'test@email.com', 'salom@salom.com', |
11 |
'ar@yahoo.com', 'lex@gmail.com', 'Tester1234@gmail.com', 'mantaf@mail.com', 'aaa@aaa.com', |
12 |
'oeui@gmail.com', 'risitesh. biswal14@yahoo.com', 'ttt@wp.pl', 'nnn@nnn.net', 'nnn2@nnn.net', |
13 |
'ana@gmail.com', 'asdf@yahoo.com', 'noom@gmail.com', 'jomon@example.com', 'asdfasdf@yahoo.com', |
14 |
'admin@yahoo.com', 'abinubli@mail.com', 'tes@tes.com', 'asdasdr@asd.com', 'something@some.com', |
15 |
'ademin@example.com', 'd@dd.com', 'robo@gmail.com', 'toto@titi.com', 'fesfe@fseff. fes', |
16 |
'master@wpthemeslist.com', 'teste@teste.com', 'barny182@hotmail.com', 'test@admin.com', |
17 |
'billtian@test.com', 'Test@goggle.ca', 'jm@gmail.com', 'john-panin@qip.ru', 'loslos@loslos.com', |
18 |
'ghfhf@jhgjgjk.com', 'lol@lol.com', 'tester1@gmail.com', 'g0952180828@gmail.com', 'testim@testim.com', |
19 |
'mnml.name@gmail.com', 'endri. azizi. 92@gmail.com', '123123@gmail.com', 'myfriend@gmai.com', |
20 |
'geraldo_1989@hotmail.com', 'rob. test. 999@gmail.com', 'j@c. com', 'Agung. andika@mhs.uinjkt.ac.id', |
21 |
'W3test@ya.ru', 'user@ya.ru', 'ed@ed. fl', 'ed@ed.es', ]; |
Sebagian besar kemungkinan dari celah waktu saat Meeting Planner baru dan tidak lagi berjalan—selama perawatan dan operasi tumor otak saya.
Baru-baru ini, dengan menambahkan login sosial, saya mendaftar ke Meeting Planner cukup mudah, namun pendaftaran spam masih dimungkinkan. Saya ingin membuatnya lebih sulit bagi orang untuk mendaftar dengan email yang buruk.
Untungnya, Yii menawarkan beberapa fitur yang mendukung hal ini.
Captcha
Yii2 sekarang menawarkan captcha bawaan. Jadi, siapa saja yang mendaftar dengan metode email dan password model lama harus memasukkan captcha. Anda dapat melihat field captcha
di bawah ini:
1 |
<p>Or, fill out the following fields to register manually:</p> |
2 |
<div class="col-lg-5"> |
3 |
<?php $form = ActiveForm::begin(['id' => 'form-signup']); ?> |
4 |
<?= $form->field($model, 'username') ?> |
5 |
<?=
|
6 |
$form->field($model, 'email', ['errorOptions' => ['class' => 'help-block' ,'encode' => false]])->textInput() ?> |
7 |
<?= $form->field($model, 'password')->passwordInput() ?> |
8 |
<?= $form->field($model, 'captcha')->widget(\yii\captcha\Captcha::classname(), [ |
9 |
// configure additional widget properties here
|
10 |
]) ?> |
11 |
<div class="form-group"> |
12 |
<?= Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?> |
13 |
</div>
|
14 |
<?php ActiveForm::end(); ?> |
15 |
</div>
|
Kemudian, kepatuhan terhadap captcha ditambahkan sebagai aturan untuk model SignupForm
:
1 |
<?php
|
2 |
namespace frontend\models; |
3 |
|
4 |
use common\models\User; |
5 |
use yii\base\Model; |
6 |
use Yii; |
7 |
use yii\helpers\Html; |
8 |
use yii\validators\EmailValidator; |
9 |
|
10 |
/**
|
11 |
* Signup form
|
12 |
*/
|
13 |
class SignupForm extends Model |
14 |
{
|
15 |
public $username; |
16 |
public $email; |
17 |
public $password; |
18 |
public $captcha; |
19 |
|
20 |
/**
|
21 |
* @inheritdoc
|
22 |
*/
|
23 |
public function rules() |
24 |
{
|
25 |
return [ |
26 |
['username', 'filter', 'filter' => 'trim'], |
27 |
['username', 'required'], |
28 |
['username', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This username has already been taken.'], |
29 |
['username', 'string', 'min' => 2, 'max' => 255], |
30 |
['email', 'filter', 'filter' => 'trim'], |
31 |
['email', 'required'], |
32 |
['email', 'email', 'checkDNS'=>true, 'enableIDN'=>true], |
33 |
['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken. '.Html::a('Looking for your password?', ['site/request-password-reset'])], |
34 |
['password', 'required'], |
35 |
['password', 'string', 'min' => 6], |
36 |
['captcha', 'required'], |
37 |
['captcha', 'captcha'], |
38 |
];
|
39 |
}
|
Jika orang-orang tidak memasukkan respons captcha yang benar, mereka tidak dapat mendaftar. Hal ini membuat registrasi otomatis sulit bagi spammer.
CheckDNS
Saya juga ingin meminimalisir registrasi dengan alamat email palsu. Validasi checkDNS
Yii sebenarnya mencari record MX yang valid berdasarkan domain alamat email:
1 |
['email', 'email', 'checkDNS'=>true, 'enableIDN'=>true], |
Jadi, misalnya, jika saya salah ketik gmail.com sebagai gmal.com, checkDNS
mengembalikan false
. Tidak ada record MX yang terdaftar untuk gmal.com. Demikian pula, tidak ada untuk spambotolympics9922.com.
Pada akhirnya, keamanan adalah proses yang berulang-ulang. Selalu ada lagi yang harus dilakukan.
Membatasi Tindakan yang Terlarang
Selanjutnya, saya ingin menambahkan batasan umum pada sejumlah tindakan yang dapat dilakukan orang-orang, untuk membatasi penyalahgunaan dan agar aplikasi tidak menjadi berat.
Pembuatan Pertemuan
Untuk mencegah orang menciptakan banyak pertemuan kosong, saya menciptakan sebuah findEmptyMeeting
yang mencari pertemuan kosong dan menggunakannya kembali ketika seseorang mencoba membuat yang baru:
1 |
public function actionCreate() |
2 |
{
|
3 |
// prevent creation of numerous empty meetings
|
4 |
$meeting_id = Meeting::findEmptyMeeting(Yii::$app->user->getId()); |
5 |
//echo $meeting_id;exit;
|
6 |
if ($meeting_id===false) { |
7 |
// otherwise, create a new meeting
|
8 |
$model = new Meeting(); |
9 |
$model->owner_id= Yii::$app->user->getId(); |
10 |
$model->sequence_id = 0; |
11 |
$model->meeting_type = 0; |
12 |
$model->save(); |
13 |
$model->initializeMeetingSetting($model->id,$model->owner_id); |
14 |
$meeting_id = $model->id; |
15 |
}
|
16 |
$this->redirect(['view', 'id' => $meeting_id]); |
17 |
}
|
Dengan kata lain, jika pengguna membuat pertemuan baru 1.700 kali, mereka akan selalu disajikan dengan pertemuan kosong pertama yang mereka buat.
Membatasi Frekuensi Tindakan
Saya juga menciptakan metode withinLimit
yang terstruktur umum untuk digunakan kembali di seputar aplikasi yang dapat mencegah terlalu banyak tindakan dalam waktu yang terlalu singkat. Contoh di bawah ini memeriksa bahwa tidak lebih dari n jumlah pertemuan telah dibuat pada jam terakhir dan hari terakhir:
1 |
public static function withinLimit($user_id,$minutes_ago = 180) { |
2 |
// how many meetings created by this user in past $minutes_ago
|
3 |
$cnt = Meeting::find() |
4 |
->where(['owner_id'=>$user_id]) |
5 |
->andWhere('created_at>'.(time()-($minutes_ago*60))) |
6 |
->count(); |
7 |
if ($cnt >= Meeting::NEAR_LIMIT ) { |
8 |
return false; |
9 |
}
|
10 |
// check in last DAY_LIMIT
|
11 |
$cnt = Meeting::find() |
12 |
->where(['owner_id'=>$user_id]) |
13 |
->andWhere('created_at>'.(time()-(24*3600))) |
14 |
->count(); |
15 |
if ($cnt >= Meeting::DAY_LIMIT ) { |
16 |
return false; |
17 |
}
|
18 |
return true; |
19 |
}
|
Kapan pun seseorang mencoba membuat pertemuan, kita memeriksa withinLimit
apakah mereka memungkinkan. Jika tidak, kita menampilkan pesan kesalahan flash
:
1 |
public function actionCreate() |
2 |
{
|
3 |
if (!Meeting::withinLimit(Yii::$app->user->getId())) { |
4 |
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.')); |
5 |
return $this->redirect(['index']); |
6 |
}
|
7 |
Membatasi Jumlah Tindakan
Saya juga ingin membatasi jumlah tindakan secara keseluruhan. Misalnya, setiap peserta pertemuan hanya dapat menambahkan tujuh kali tanggal pertemuan per pertemuan. Dalam MeetingTime.php, saya menetapkan MEETING_LIMIT
, jadi bisa diubah nantinya:
1 |
const MEETING_LIMIT = 7; |
Kemudian, MeetingTime::withinLimit()
memeriksa untuk memastikan bahwa tidak lebih dari tujuh kali telah disarankan oleh pengguna manapun:
1 |
public static function withinLimit($meeting_id) { |
2 |
// how many meetingtimes added to this meeting
|
3 |
$cnt = MeetingTime::find() |
4 |
->where(['meeting_id'=>$meeting_id]) |
5 |
->count(); |
6 |
// per user limit option: ->where(['suggested_by'=>$user_id])
|
7 |
if ($cnt >= MeetingTime::MEETING_LIMIT ) { |
8 |
return false; |
9 |
}
|
10 |
return true; |
11 |
}
|
Ketika mereka akan membuat MeetingTime
, controller membuat metode memeriksa batas-batasnya:
1 |
public function actionCreate($meeting_id) |
2 |
{
|
3 |
if (!MeetingTime::withinLimit($meeting_id)) { |
4 |
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.')); |
5 |
return $this->redirect(['/meeting/view', 'id' => $meeting_id]); |
6 |
}
|
7 |
Mengamankan Tugas CRON
Akhirnya hari ini, saya ingin mengamankan akses ke tugas cron jarak jauh. Ada beberapa pendekatan menarik yang dijelaskan di interwebs. Untuk saat ini, saya memeriksa bahwa $_SERVER['REMOTE_ADDR']
(alamat IP yang meminta) adalah server yang sama dengan hosting $_SERVER['SERVER_ADDR']
, alamat IP lokal. $_SERVER['REMOTE_ADDR']
aman digunakan untuk keamanan—dengan kata lain, saya telah membaca bahwa itu tidak dapat dipalsukan.
1 |
// only cron jobs and admins can run this controller's actions
|
2 |
public function beforeAction($action) |
3 |
{
|
4 |
// your custom code here, if you want the code to run before action filters,
|
5 |
// which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl
|
6 |
if (!parent::beforeAction($action)) { |
7 |
return false; |
8 |
}
|
9 |
// other custom code here
|
10 |
if (( $_SERVER['REMOTE_ADDR'] == $_SERVER['SERVER_ADDR'] ) || |
11 |
(!\Yii::$app->user->isGuest && \common\models\User::findOne(Yii::$app->user->getId())->isAdmin())) |
12 |
{
|
13 |
return true; |
14 |
}
|
15 |
return false; // or false to not run the action |
16 |
}
|
Untuk pengujian saya sendiri, saya juga mengizinkan administrator yang login untuk menjalankan tugas cron.
Akhirnya, saya juga bisa menambahkan kata sandi ke tugas cron saya dan memindahkannya ke operasi command-line.
Melihat ke Depan
Saya telah menyelesaikan banyak perbaikan keamanan selama dua episode terakhir, namun masih banyak yang harus dilakukan. Pada daftar pendek saya adalah tinjauan keamanan akses yang lebih dalam, terutama melalui AJAX, pelacakan dan pemblokiran alamat IP, dan dengan hati-hati memfilter semua masukan pengguna.
Sekali lagi, tunggu apa lagi? Jadwalkan pertemuan pertama Anda, dan bagikan masukan Anda di komentar. Saya juga menghargai komentar Anda tentang masalah keamanan.
Seperti biasa, Anda bisa melihat tutorial yang akan datang di serial Membangun Startup Anda dengan PHP atau ikuti saya @reifman. Ada beberapa fitur besar yang akan muncul.