CodeIgniter 4 Shieldv1.2.x
Registrasi & Urutan Filter
Halaman ini menjelaskan apa yang sebenarnya didaftarkan oleh
ShieldServiceProvider, kapan filter Shield berjalan, dan bagaimana membuat
request 404/scanner tetap diperiksa sebelum router tanpa memeriksa request
dua kali.
Yang didaftarkan provider
Section titled “Yang didaftarkan provider”ShieldServiceProvider::register() bukan hanya memasang filter. Ia
mendaftarkan:
| Yang didaftarkan | Keterangan |
|---|---|
shield.resolver |
Resolver konfigurasi, engine, cache, dan challenge. |
shield.config |
Konfigurasi Shield yang sudah dibangun. |
shield.engine |
Mesin keputusan Shield. |
shield.cache |
Adapter cache CodeIgniter. |
shield.challenge |
Driver challenge (Turnstile, reCAPTCHA, atau null). |
Alias shield.firewall |
Alias untuk SecurityFirewallFilter. |
globals['before'][] = 'shield.firewall' |
Penempatan filter default. |
Command shield:* |
Hanya jika aplikasi punya Config\Commands; jika tidak, CodeIgniter menemukannya lewat PSR-4 package. |
Memanggil provider berulang aman: service dibangun ulang dari konfigurasi terkini. Tanpa provider, komponen Shield masih punya fallback internal, tetapi alias filter, service bersama, dan penempatan filter default tidak terpasang.
Urutan filter CodeIgniter 4
Section titled “Urutan filter CodeIgniter 4”Urutan eksekusi filter di CodeIgniter 4 adalah:
required['before'] → routing → globals['before'] → controller → globals['after'] → required['after']Konsekuensinya penting:
required['before']berjalan sebelum router. Filter di sini tetap dijalankan meskipun tidak ada route yang cocok, termasuk untuk/.env,/wp-login.php, dan path acak.globals['before']berjalan setelah router. Filter di sini hanya melihat request yang berhasil di-route. Request ke path tanpa route sudah berhenti diPageNotFoundExceptionsebelum filter sempat berjalan.required['after']tetap berjalan untuk response 404. Ini yang dipakai untuk menghitungnot_foundpada path yang tidak punya route.
Registrasi tanpa gate (tidak disarankan untuk aplikasi publik)
Section titled “Registrasi tanpa gate (tidak disarankan untuk aplikasi publik)”Registrasi paling sederhana adalah memanggil provider di
app/Config/Events.php:
Events::on('pre_system', static function (): void { \Ganadev\Shield\Codeigniter\ShieldServiceProvider::register();});Dengan cara ini Shield berjalan di globals['before'] — perilaku bawaan paket.
Ini hanya cocok kalau:
- aplikasi tidak menghadap publik langsung, atau
- path scanner sudah ditangani WAF/edge di depan server.
Batasan yang harus Anda sadari: /.env, path traversal, dan scanner path acak
tidak diperiksa oleh Shield karena router sudah lebih dulu melempar 404.
Karena itu Instalasi memakai pola gate di bawah.
Registrasi sebelum router (default)
Section titled “Registrasi sebelum router (default)”Untuk aplikasi publik, pola yang dipakai di
Instalasi adalah memindahkan Shield
ke required['before'] lewat filter gate. Shield tetap memeriksa semua request,
tetapi halaman publik yang normal tidak menambah behavior counter.
1. Tetap daftarkan provider
Section titled “1. Tetap daftarkan provider”Provider tetap wajib dipanggil karena ia mendaftarkan service, alias, dan konfigurasi bersama.
2. Hapus shield.firewall dari global filter
Section titled “2. Hapus shield.firewall dari global filter”Provider menambahkan shield.firewall ke globals['before']. Kalau gate juga
dipasang di required, request akan diperiksa dua kali dan counter bisa naik
dua kali. Hapus penempatan global itu setelah provider dipanggil:
Events::on('pre_system', static function (): void { \Ganadev\Shield\Codeigniter\ShieldServiceProvider::register();
/* * Provider memasang Shield sebagai filter global, yang berjalan setelah * routing. Kita memakai App\Filters\ShieldGateFilter di required filter * agar path tanpa route juga diperiksa. Hapus yang global supaya request * tidak diperiksa dua kali. */ $filters = config('Filters');
$filters->globals['before'] = array_values(array_filter( $filters->globals['before'] ?? [], static fn ($alias): bool => $alias !== 'shield.firewall', ));});Kalau app/Config/Events.php Anda sudah berisi kode lain, gabungkan potongan di
atas dengan isi yang sudah ada; jangan menimpa seluruh file.
3. Buat ShieldGateFilter
Section titled “3. Buat ShieldGateFilter”Buat app/Filters/ShieldGateFilter.php:
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;use CodeIgniter\HTTP\RequestInterface;use CodeIgniter\HTTP\ResponseInterface;use Ganadev\Shield\Codeigniter\Cache\CodeIgniterCacheAdapter;use Ganadev\Shield\Codeigniter\Filters\SecurityFirewallFilter;
/** * Menjalankan Shield sebelum router, tetapi mencegah kunjungan normal ke * halaman publik menambah behavior counter. Ban dan challenge yang sudah ada * tetap ditegakkan. */final class ShieldGateFilter implements FilterInterface{ private ?SecurityFirewallFilter $firewall = null;
public function before(RequestInterface $request, $arguments = null): ?ResponseInterface { if (! $this->isPublicPage($request)) { return $this->firewall()->before($request, $arguments); }
// Shield menaikkan counter unique/path sebelum memutuskan. Simpan nilai // lama agar kenaikan itu bisa dibatalkan untuk page view normal. $snapshot = $this->snapshotCounters($request); $result = $this->firewall()->before($request, $arguments);
// Kalau Shield mengizinkan, kembalikan counter. Kalau block/challenge, // biarkan kenaikannya agar histori tetap tercatat. if ($result === null) { $this->restoreCounters($snapshot); }
return $result; }
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void { if ($this->isPublicPage($request) || $response->hasHeader('X-Shield-Blocked')) { return; }
$this->firewall()->after($request, $response, $arguments); }
private function firewall(): SecurityFirewallFilter { return $this->firewall ??= new SecurityFirewallFilter(); }
private function isPublicPage(RequestInterface $request): bool { // Hanya page view normal. POST/PUT/PATCH/DELETE selalu diperiksa penuh // agar submit form tidak bisa dipakai untuk melewati Shield. if (! in_array(strtoupper($request->getMethod()), ['GET', 'HEAD'], true)) { return false; }
$path = $this->normalizePath($request);
$paths = config('Shield')->gateSkipPaths ?? ['/', '/health'];
return in_array($path, $paths, true); }
/** * @return array{uniqueKey: string, pathKey: string, unique: mixed, path: mixed}|null */ private function snapshotCounters(RequestInterface $request): ?array { $cache = $this->shieldCache();
if ($cache === null) { return null; }
$ip = $request->getIPAddress(); $normalizedPath = strtolower((string) preg_replace('#/{2,}#', '/', $this->normalizePath($request)));
$uniqueKey = "counters:{$ip}:unique"; $pathKey = 'counters:'.$ip.':path:'.hash('sha256', $normalizedPath);
return [ 'uniqueKey' => $uniqueKey, 'pathKey' => $pathKey, 'unique' => $cache->get($uniqueKey), 'path' => $cache->get($pathKey), ]; }
/** * @param array{uniqueKey: string, pathKey: string, unique: mixed, path: mixed}|null $snapshot */ private function restoreCounters(?array $snapshot): void { if ($snapshot === null) { return; }
$cache = $this->shieldCache();
if ($cache === null) { return; }
$ttl = (int) (config('Shield')->behaviorWindowSeconds ?? 60);
if ($snapshot['unique'] === null) { $cache->delete($snapshot['uniqueKey']); } else { $cache->set($snapshot['uniqueKey'], (int) $snapshot['unique'], $ttl); }
if ($snapshot['path'] === null) { $cache->delete($snapshot['pathKey']); } else { $cache->set($snapshot['pathKey'], (int) $snapshot['path'], $ttl); } }
private function shieldCache(): ?CodeIgniterCacheAdapter { try { $cache = service('shield.cache');
return $cache instanceof CodeIgniterCacheAdapter ? $cache : null; } catch (\Throwable) { return null; } }
private function normalizePath(RequestInterface $request): string { $path = $request->getPath() ?: '/';
return $path[0] === '/' ? $path : '/'.$path; }}4. Daftarkan alias dan posisi filter
Section titled “4. Daftarkan alias dan posisi filter”Di app/Config/Filters.php, tambahkan alias dan tempatkan gate di required:
use App\Filters\ShieldGateFilter;
public array $aliases = [ // ... 'shield.gate' => ShieldGateFilter::class,];
public array $required = [ 'before' => [ 'forcehttps', // hapus kalau aplikasi tidak memakai ForceHTTPS 'shield.gate', 'pagecache', // hapus kalau aplikasi tidak memakai PageCache ], 'after' => [ 'pagecache', 'performance', 'toolbar', 'shield.gate', ],];Pertahankan filter lain yang sudah ada di aplikasi Anda. Yang penting:
shield.gateberada direquired['before'], setelahforcehttps(kalau dipakai) dan sebelumpagecache.shield.gatejuga berada direquired['after']agar 404 sebelum router tetap dihitung.shield.firewalltidak lagi ada diglobals['before'].
5. Tambahkan daftar path publik
Section titled “5. Tambahkan daftar path publik”Di app/Config/Shield.php, tambahkan properti aplikasi berikut:
/** * Path publik yang tetap diperiksa Shield, tetapi tidak menambah behavior * counter saat diakses normal dengan GET/HEAD. * * Ini bukan daftar bypass. Ban, challenge, dan rule enforcement tetap berlaku. * * @var list<string> */public array $gateSkipPaths = [ '/', '/health', '/robots.txt', '/sitemap.xml', '/favicon.ico', '/tentang', '/kontak',];gateSkipPaths adalah konfigurasi aplikasi, bukan config bawaan paket. Jangan
masukkan endpoint yang menangani data sensitif atau submit form.
Kenapa perlu gate filter?
Section titled “Kenapa perlu gate filter?”SecurityFirewallFilter::before() memanggil readCounters() sebelum
engine memutuskan. Artinya setiap request menaikkan:
counters:{ip}:unique— jumlah URI berbeda.counters:{ip}:path:{hash}— jumlah request ke path tersebut.
Kunjungan normal ke / berulang kali bisa menaikkan uniqueUriCount dan
pathRequestCount sampai ambang challenge, padahal pengunjung hanya membuka
halaman publik. Gate filter menyelesaikan ini dengan:
- Mengambil snapshot dua counter tersebut.
- Menjalankan Shield seperti biasa.
- Mengembalikan counter hanya jika Shield mengizinkan request.
Kalau Shield memblokir atau meminta challenge, counter tidak dikembalikan. Jadi IP yang sudah mencurigakan tetap kena challenge/block saat membuka halaman publik.
Aturannya sengaja dibatasi:
- Hanya
GET/HEADyang di-rollback. POST/PUT/PATCH/DELETEselalu diperiksa penuh.gateSkipPathstidak melewati pemeriksaan Shield; hanya menentukan path mana yang counter-nya dikembalikan saat request diizinkan.
Urutan dengan PageCache
Section titled “Urutan dengan PageCache”Kalau aplikasi memakai pagecache, urutan yang aman adalah:
forcehttps → shield.gate → pagecachePageCache tidak boleh berjalan sebelum Shield. Kalau cache lebih dulu, response yang seharusnya diblokir bisa tersaji dari cache tanpa pernah diperiksa Shield.
Verifikasi
Section titled “Verifikasi”Periksa urutan filter:
php spark filter:check GET /Yang diharapkan: forcehttps → shield.gate → pagecache (sesuaikan dengan filter
yang benar-benar dipakai aplikasi Anda).
Uji perilaku dengan curl:
| Skenario | Hasil yang diharapkan |
|---|---|
GET / 5x berturut-turut |
200, tidak ada event baru, counter tidak menumpuk. |
GET /health |
200, halaman publik aman. |
GET /.env |
Diperiksa sebelum router; pada mode enforce/challenge diblokir dengan X-Shield-Blocked. Pada observe hanya dicatat. |
GET /random-path |
Tetap diperiksa dan 404-nya dihitung. |
POST / |
Diperiksa penuh, tidak melewati gate. |
GET / setelah /.env diblokir |
Tetap kena challenge/block sesuai skor IP. |
Batasan
Section titled “Batasan”- Bukan pengganti WAF edge.
required['before']tetap berjalan setelah PHP dieksekusi. Untuk memblokir sebelum PHP, tetap gunakan Cloudflare, Nginx, ModSecurity, atau aturan web server. - Snapshot/rollback bukan operasi atomik. Dua request publik bersamaan dari IP yang sama bisa menghasilkan undercount sementara. Untuk traffic normal dampaknya kecil.
- TTL counter di-reset. Rollback menulis ulang counter dengan TTL penuh
behaviorWindowSeconds. Nilainya tidak berubah, hanya masa berlakunya. - Daftar path harus dipelihara. Tambahkan halaman publik baru ke
gateSkipPathsbila perlu; jangan masukkan endpoint sensitif. - Terikat internal v1.2.x. Format key counter dan header
X-Shield-Blockedbisa berubah di rilis berikutnya. Periksa ulang saat upgrade.
Baca juga
Section titled “Baca juga”- Instalasi — langkah pemasangan.
- Model Keamanan — batas dan tanggung jawab Shield.
- Perilaku & Bot — cara counter dan 404 dipakai.
- Masalah Umum — gejala dan perbaikan.
Powered by PT Ganadev Multi Solusi