Tutorial Pasukan Lucee MY

Borang tambah produk dan pertahanan suntikan SQL

Bina borang tambah produk dalam CFML: sahkan input, tunjuk ralat di sebelah medan, dan simpan dengan cfqueryparam supaya selamat daripada suntikan SQL.

Borang tambah produk dan pertahanan suntikan SQL
0
Kandungan

Borang tambah produk dan pertahanan suntikan SQL

Setakat ini, produk hanya boleh ditambah melalui phpMyAdmin, seperti yang diterangkan dalam bab pangkalan data. Itu tidak boleh diterima untuk aplikasi sebenar, kerana pemilik kedai tidak akan membuka alat pangkalan data setiap kali menambah barang.

Bab ini membina borang tambah produk, menggunakan susun atur yang anda bina dalam bab header dan jadual. Ia juga memperkenalkan perkara paling penting dalam keselamatan aplikasi: menerima input daripada orang lain tanpa membenarkan input itu mengubah arahan pangkalan data.

Satu halaman, dua tugas

Fail produk-tambah.cfm melakukan dua perkara bergantung kepada cara ia dibuka.

Apabila anda membuka alamatnya dalam pelayar, ia memaparkan borang kosong. Apabila anda menekan butang Simpan, pelayar menghantar data ke alamat yang sama, dan halaman itu menyimpan data tersebut.

Corak ini dipanggil borang yang menghantar kepada dirinya sendiri. Ia mudah difahami untuk pemula, kerana semua logik berada dalam satu fail.

Bezakan GET dan POST

Pelayar boleh meminta halaman dengan dua cara.

GET ialah permintaan biasa, seperti ketika anda menaip alamat atau mengklik pautan. Ia sepatutnya hanya membaca.

POST ialah penghantaran data, seperti ketika anda menekan butang Simpan. Ia boleh mengubah keadaan.

Peraturan mudah: apa-apa yang mengubah data mesti melalui POST. Ini sama seperti gelung yang anda pelajari dalam bab nilai dan keputusan: satu corak, diulang dalam konteks berbeza. Jangan sekali-kali menyimpan atau memadam melalui pautan GET, kerana pautan boleh diklik oleh crawler atau dimuat semula tanpa sengaja.

Tulis borangnya dahulu

Cipta fail produk-tambah.cfm di dalam C:\inventori, dan taip kod ini:

<cfinclude template="includes/atas.cfm">

<cfset ralat = {}>
<cfset nilai = { sku: "", nama: "", harga: "", stok: "0" }>

<cfif structKeyExists(form, "hantar")>
    <cfset nilai.sku = trim(form.sku ?: "")>
    <cfset nilai.nama = trim(form.nama ?: "")>
    <cfset nilai.harga = trim(form.harga ?: "")>
    <cfset nilai.stok = trim(form.stok ?: "0")>

    <cfif len(nilai.sku) EQ 0>
        <cfset ralat.sku = "SKU tidak boleh kosong.">
    <cfelseif NOT reFind("^[A-Za-z0-9-]{3,40}$", nilai.sku)>
        <cfset ralat.sku = "SKU mesti 3 hingga 40 aksara: huruf, nombor atau sengkang.">
    </cfif>

    <cfif len(nilai.nama) EQ 0>
        <cfset ralat.nama = "Nama produk tidak boleh kosong.">
    <cfelseif len(nilai.nama) GT 150>
        <cfset ralat.nama = "Nama produk terlalu panjang.">
    </cfif>

    <cfif NOT isNumeric(nilai.harga)>
        <cfset ralat.harga = "Harga mesti nombor, contohnya 12.50.">
    <cfelseif nilai.harga LT 0>
        <cfset ralat.harga = "Harga tidak boleh negatif.">
    </cfif>

    <cfif NOT isNumeric(nilai.stok) OR nilai.stok LT 0>
        <cfset ralat.stok = "Stok mesti nombor bulat sifar atau lebih.">
    </cfif>

    <cfif structIsEmpty(ralat)>
        <cfquery datasource="inventori">
            INSERT INTO produk (sku, nama, harga, stok)
            VALUES (
                <cfqueryparam value="#ucase(nilai.sku)#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#nilai.nama#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#nilai.harga#" cfsqltype="cf_sql_decimal" scale="2">,
                <cfqueryparam value="#nilai.stok#" cfsqltype="cf_sql_integer">
            )
        </cfquery>
        <cflocation url="index.cfm?berjaya=1" addtoken="false">
    </cfif>
</cfif>

<h2>Tambah produk</h2>

<cfif NOT structIsEmpty(ralat)>
    <div class="ralat-kotak">Sila betulkan medan yang bertanda.</div>
</cfif>

<cfoutput>
<form method="post" action="produk-tambah.cfm" class="borang">
    <label>SKU
        <input type="text" name="sku" value="#encodeForHTMLAttribute(nilai.sku)#" maxlength="40">
        <cfif structKeyExists(ralat, "sku")><span class="ralat">#ralat.sku#</span></cfif>
    </label>

    <label>Nama produk
        <input type="text" name="nama" value="#encodeForHTMLAttribute(nilai.nama)#" maxlength="150">
        <cfif structKeyExists(ralat, "nama")><span class="ralat">#ralat.nama#</span></cfif>
    </label>

    <label>Harga (RM)
        <input type="text" name="harga" value="#encodeForHTMLAttribute(nilai.harga)#" inputmode="decimal">
        <cfif structKeyExists(ralat, "harga")><span class="ralat">#ralat.harga#</span></cfif>
    </label>

    <label>Stok awal
        <input type="text" name="stok" value="#encodeForHTMLAttribute(nilai.stok)#" inputmode="numeric">
        <cfif structKeyExists(ralat, "stok")><span class="ralat">#ralat.stok#</span></cfif>
    </label>

    <button type="submit" name="hantar" value="1">Simpan produk</button>
</form>
</cfoutput>

<cfinclude template="includes/bawah.cfm">

Anda juga perlu menambah gaya untuk borang. Letakkan baris ini di dalam <style> dalam includes/atas.cfm:

.borang { background:#fff; padding:22px; border-radius:10px; max-width:520px; }
.borang label { display:block; margin-bottom:16px; font-weight:600; font-size:0.92rem; }
.borang input { display:block; width:100%; margin-top:6px; padding:9px 11px; border:1px solid #cbd5e1; border-radius:7px; font-size:1rem; font-weight:400; }
.borang button { background:#0b6b5f; color:#fff; border:0; padding:11px 18px; border-radius:8px; font-size:0.95rem; cursor:pointer; }
.ralat { display:block; color:#b91c1c; font-size:0.85rem; font-weight:400; margin-top:5px; }
.ralat-kotak { background:#fef2f2; border:1px solid #fecaca; color:#991b1b; padding:11px 14px; border-radius:8px; margin-bottom:16px; max-width:520px; }
.butang { display:inline-block; background:#0b6b5f; color:#fff; text-decoration:none; padding:9px 15px; border-radius:8px; font-size:0.9rem; }

Akhir sekali, tambah pautan dari halaman utama. Dalam index.cfm, letakkan baris ini sebelum <table>:

<p class="tindakan"><a class="butang" href="produk-tambah.cfm">+ Tambah produk</a></p>

Bagaimana pengesahan berfungsi

Apabila butang ditekan, structKeyExists(form, "hantar") menjadi benar, dan blok pengesahan berjalan.

Setiap medan disemak satu demi satu, dan mesej disimpan ke dalam struct ralat mengikut nama medan. Contohnya ralat.sku.

Selepas semua semakan, structIsEmpty(ralat) memberitahu kita sama ada ada masalah. Kalau kosong, kita simpan. Kalau tidak, borang dipaparkan semula dengan mesej di sebelah medan yang salah.

Borang dengan mesej ralat di sebelah setiap medan

Hasil sebenar pada rig ujian kami: setiap medan yang salah mendapat mesej sendiri.

Perhatikan bahawa nilai yang ditaip disimpan semula ke dalam medan. Tanpa itu, pengguna perlu menaip semula semuanya hanya kerana satu medan salah. Itu pengalaman yang menyakitkan dan mudah dielakkan.

Pertahanan suntikan SQL

Inilah bahagian paling penting dalam bab ini.

Bayangkan seseorang menaip ini ke dalam medan SKU:

ABC'; DROP TABLE produk; --

Kalau kita membina SQL dengan mencantum teks, arahan itu menjadi sebahagian daripada query, dan pangkalan data akan cuba menjalankannya. Itu dipanggil suntikan SQL.

<cfqueryparam> menghalangnya. Ia menghantar nilai sebagai data, bukan sebagai arahan. Contoh serangan sebenar ada dalam artikel suntikan SQL ini. Pangkalan data menerima teks itu sebagai satu nilai SKU yang pelik, bukan sebagai arahan baharu.

Peraturan yang perlu anda pegang mulai sekarang: setiap nilai yang datang daripada borang, URL atau cookie mesti melalui <cfqueryparam>. Tiada pengecualian.

Jenis datacfsqltype
Teks pendekcf_sql_varchar
Nombor bulatcf_sql_integer
Wangcf_sql_decimal
Tarikhcf_sql_timestamp

Mengapa kita melencong selepas simpan

Selepas menyimpan, kod memanggil <cflocation url="index.cfm?berjaya=1">. Ini memindahkan pelayar ke halaman utama, dan halaman itu memaparkan mesej berjaya.

Corak ini dipanggil POST-Redirect-GET. Ia menghalang pelayar daripada menghantar semula data yang sama apabila pengguna menekan butang refresh. Tanpanya, satu produk boleh tersimpan dua kali hanya kerana pengguna menekan refresh.

Ralat yang saya sendiri lalui

Semasa menulis bab ini, mesej ralat muncul sebagai teks mentah #ralat.sku# dan bukan mesej sebenar. Puncanya mudah: blok <cfif> itu berada di luar <cfoutput>.

CFML hanya memproses #...# di dalam <cfoutput>. Di luar, ia mencetak tanda itu sebagai teks biasa. Penyelesaiannya ialah membalut borang dengan <cfoutput>. Itu sebabnya anda nampak <cfoutput> dalam kod di atas.

Ralat lazim lain

Semua medan dianggap kosong walaupun anda mengisinya. Pastikan borang menggunakan method="post", bukan get.

Produk tersimpan dua kali. Biasanya kerana cflocation tiada, atau kerana pengguna menekan refresh selepas POST.

SKU pendua menyebabkan ralat pangkalan data. Skema kita mempunyai kekangan unik pada SKU. Untuk pemula, cara paling mudah ialah menyemak dahulu dengan SELECT sebelum INSERT, dan kita akan tambah semakan itu dalam bab ubah.

Mesej ralat tidak muncul. Semak sama ada ralat benar-benar diisi, dan sama ada <cfif structKeyExists(ralat, "sku")> menggunakan nama medan yang betul.

Satu perkara yang belum ada

Borang ini belum mempunyai token CSRF. Itu bermakna laman lain boleh menghantar borang ke aplikasi anda atas nama pengguna yang sedang log masuk.

Kita akan menambahnya dalam bab keselamatan, selepas log masuk wujud. Untuk aplikasi yang berjalan di komputer sendiri, risiko itu kecil. Untuk laman awam, ia wajib.

Apa yang anda sudah ada

Anda kini boleh menambah produk dari laman sendiri, dengan pengesahan dan pertahanan suntikan SQL. Itu sudah cukup untuk menggantikan phpMyAdmin dalam kerja harian.

Bab seterusnya menambah keupayaan mengubah dan mengarkibkan produk, supaya data yang tersilap masuk boleh dibetulkan tanpa memadam sejarah.

Soalan Lazim

Boleh saya menggunakan type="number" untuk medan harga? Boleh, dan pelayar akan menyekat sesetengah input. Tetapi pengesahan pelayan tetap wajib, kerana pelayar boleh dipintas. Kita menyemak semula dalam CFML.

Kenapa SKU ditukar kepada huruf besar? Supaya brs-001 dan BRS-001 dianggap sama. Tanpanya, dua produk yang sama boleh wujud dengan ejaan berbeza.

Adakah isNumeric menerima 12,50? Tidak. Ia menerima titik perpuluhan. Untuk pemula, minta pengguna menaip 12.50, dan tambah mesej contoh dalam medan.

Boleh saya tambah medan kategori sekarang? Boleh, tetapi jadual kategori belum wujud dalam siri ini. Ia datang dalam bab ubah dan arkib, supaya setiap bab menambah satu perkara.

Mengapa tidak guna cfform? Kerana HTML biasa lebih mudah difahami, dan ia berfungsi pada mana-mana pelayar. cfform menambah lapisan yang menyukarkan pemula memahami apa yang sebenarnya dihantar.

Sumber

Log masuk untuk menyimpan kemajuan bacaan anda.

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk