Grafikus C# – Windows Forms tanfolyam › 3.3 lecke Windows Forms · 3.3 lecke · biztonságos adatkezelés Egy bejelentkező ablak az első olyan program, amelynél a biztonság is számít. A felhasználó által beírt szöveget soha nem szabad egyszerűen az SQL parancsba fűzni, mert az adatbázis a beírt szöveget is parancsnak veheti. Ebben a leckében megírjuk a bejelentkezést, és megtanuljuk a biztonságos megoldást: a paraméterezett lekérdezést.Bejelentkezés SQLite-tal – paraméterezett lekérdezés

A lecke végére
- bejelentkező ablakot készítesz jelszómezővel;
- érted, mi az SQL-befecskendezés (SQL injection), és miért veszélyes;
- paraméterezett lekérdezést írsz (
@nev,Parameters.AddWithValue); - az adatbázis-műveletet külön metódusba (
DB.BelepesJo) teszed, amelyboolértéket ad vissza; - nem árulod el a felhasználónak, hogy a név vagy a jelszó volt-e hibás;
- felismered a bejelentkezés tipikus hibáit.
Mielőtt elkezded
Ehhez a leckéhez kell a 3.2 lecke (DB.cs, kapcsolat), az 1.9 lecke (jelszómező), az 1.10 lecke (visszajelzés) és az 1.11 lecke (bool visszatérési értékű metódus). A belepes.db adatbázisban legyen legalább egy felhasználó (3.1 lecke).
A felület
Új Windows Forms projektet használj a 3.2 leckéből (NuGet csomag, belepes.db, DB.cs), vagy bővítsd azt. Tegyél az ablakra:
TextBox a felhasználónévnek.TextBox a jelszónak, UseSystemPasswordChar: True (1.9 lecke).Belépés felirattal. Az ablak AcceptButton tulajdonsága legyen ez a gomb, hogy az Enter is működjön.Label a visszajelzéshez (1.10 lecke).Mit tegyél ide: a bejelentkező ablak tervezőnézete: két beviteli mező címkékkel (Felhasználónév, Jelszó), a Belépés gomb és az üzenet-felirat.Javasolt fájlnév: winforms-3-3-felulet.webp · Alt szöveg: A bejelentkező ablak felülete két beviteli mezővel és gombbal
A veszély: SQL-befecskendezés
A kézenfekvő, de veszélyes megoldás az, ha a beírt szöveget közvetlenül az SQL szövegébe fűzöd:
string sql = "SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = '" + txtFelhasznalo.Text
+ "' AND jelszo = '" + txtJelszo.Text + "'";Normál bevitelnél ez jól működik. De mi történik, ha a felhasználó nem névvel és jelszóval, hanem az SQL nyelv egy darabjával tölti ki a mezőket? Az adatbázis nem tudja, hogy melyik rész a program parancsa, és melyik a felhasználó adata: az egész egyetlen szöveg. Így a beírt szöveg átalakíthatja a lekérdezés jelentését. Ezt nevezik SQL-befecskendezésnek (angolul SQL injection), és az egyik leggyakoribb és legsúlyosabb webes és alkalmazásbiztonsági hiba.
Jósolj!
A felhasználónév mezőbe egyetlen aposztrófot (') írsz, a jelszó mezőbe bármit. Mi történik a fenti, összefűzött SQL-lel?
Jóslat ellenőrzése
Az aposztróf az SQL-ben a szöveg határolója. Az összefűzött SQL-ben a határolók felborulnak, és az SQL szintaktikailag hibás lesz: a program hibaüzenetet ad, vagy összeomlik. Már ez is jelzi, hogy a felhasználó beavatkozik a parancs szerkezetébe. Egy rosszindulatú felhasználó pedig úgy írja meg a szöveget, hogy a parancs érvényes marad, de más lesz a jelentése, például a feltétel mindig igaz lesz, és jelszó nélkül is beengedi.
Ez nem csak a bejelentkezésnél baj
Ugyanaz a hiba fenyeget minden olyan helyen, ahol a felhasználó szövege az SQL-be kerül: keresőmezőben, regisztrációnál, szerkesztésnél. Az ilyen hibán keresztül adatokat lehet kiolvasni, módosítani és törölni is. Ezért tanuljuk meg az egyetlen helyes megoldást: soha ne fűzz felhasználói szöveget az SQL-be.
A megoldás: paraméterezett lekérdezés
A biztonságos megoldásban az SQL szövegében helyfoglaló jelek (paraméterek) állnak, amelyek @ jellel kezdődnek (@nev, @jelszo). A tényleges értékeket külön adjuk át az SQL parancsnak. Az adatbázis így tudja, hogy a parancs szerkezete rögzített, a paraméterek pedig csak adatok, amelyeket sosem értelmez parancsként, bármi áll is bennük (akár aposztróf is):
string sql = "SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = @nev AND jelszo = @jelszo";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@nev", nev);
parancs.Parameters.AddWithValue("@jelszo", jelszo);
// ... ExecuteScalar ...
}@nev helyére a nev változó tartalma kerül, adatként.sql változó tartalma sosem függ a felhasználó bevitelétől.Jósolj!
A felhasználónévnek az O'Brien nevet adod meg paraméterként. Szintaktikai hibát okoz az aposztróf a névben?
Jóslat ellenőrzése
Nem. A paraméter értéke soha nem kerül az SQL szövegébe, hanem külön, adatként jut el az adatbázishoz. Az aposztróf így egyszerű karakter, és a keresés az O'Brien névre pontosan úgy működik, mint bármelyik másikra.
A teljes kód: DB.BelepesJo és a gomb
Az adatbázis-műveletet a DB osztályba tesszük egy metódusként, amely igaz/hamis választ ad: jó-e a belépési adatpár. Az ablak csak ezt a választ használja fel (1.11 lecke: bool visszatérési érték). Így az adatbázis-kód és a felület külön marad:
public static bool BelepesJo(string nev, string jelszo)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();
string sql = "SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = @nev AND jelszo = @jelszo";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@nev", nev);
parancs.Parameters.AddWithValue("@jelszo", jelszo);
int darab = Convert.ToInt32(parancs.ExecuteScalar());
return darab > 0;
}
}
}private void Uzenet(string szoveg, bool hiba)
{
lblUzenet.Text = szoveg;
lblUzenet.ForeColor = hiba ? Color.Firebrick : Color.SeaGreen;
}
private void btnBelep_Click(object sender, EventArgs e)
{
string nev = txtFelhasznalo.Text.Trim();
string jelszo = txtJelszo.Text;
if (nev.Length == 0 || jelszo.Length == 0)
{
Uzenet("Mindkét mezőt töltsd ki!", true);
return;
}
try
{
if (DB.BelepesJo(nev, jelszo))
{
Uzenet($"Sikeres belépés, {nev}!", false);
}
else
{
Uzenet("Hibás felhasználónév vagy jelszó.", true);
}
}
catch (Exception ex)
{
MessageBox.Show("Adatbázis-hiba: " + ex.Message);
}
}DB osztály metódusa: két szöveget kap, és igaz/hamis választ ad vissza.COUNT(*) legalább 1, létezik ilyen név-jelszó pár: a belépés jó. Mivel a név egyedi (UNIQUE, 3.1 lecke), az eredmény 0 vagy 1.return kilép a metódusból.Jósolj!
Az adatbázisban van egy kata felhasználó almafa jelszóval. Mit ír ki a program, ha a nevet kata, a jelszót Almafa (nagy A-val) írod be?
Jóslat ellenőrzése
Hibás belépést: az SQLite szövegek összehasonlításánál az = alapból megkülönbözteti a kis- és nagybetűt, ezért az Almafa és az almafa nem azonos. A jelszó így helyes viselkedés. A felhasználónévnél viszont kényelmes lehet a nagybetű-érzéketlenség (felhnev = @nev COLLATE NOCASE), amelyet a feladatok között kipróbálhatsz.
Mit tegyél ide: két kép egymás mellett a futó programról: zöld „Sikeres belépés, kata!” üzenet, és piros „Hibás felhasználónév vagy jelszó.” üzenet.Javasolt fájlnév: winforms-3-3-belepes.webp · Alt szöveg: A bejelentkező ablak sikeres és sikertelen belépés után
További biztonsági szabályok
Hibakeresés: te vagy a hibadetektív
Mi a baj, és hogyan javítod?
Gondold végig az okot, és csak utána nyisd le a megoldást.
1. Összefűzött SQL
string sql = "SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = '" + nev + "'";Hiba és javítás
Biztonsági hiba: a felhasználó szövege az SQL részévé válik (SQL-befecskendezés), és egy aposztrófos név hibát is okoz. Használj paramétert: WHERE felhnev = @nev és parancs.Parameters.AddWithValue("@nev", nev);
2. Hiányzó paraméterérték
parancs.Parameters.AddWithValue("@nev", nev);
// az @jelszo paraméternek nem adtunk értéketHiba és javítás
Az SQL szövegében szereplő minden paraméternek értéket kell adni. Ha az @jelszo kimarad, a program hibával áll meg, vagy hibásan működik. Ellenőrizd, hogy minden @ jeles névhez van AddWithValue sor, és a nevek betűre pontosan egyeznek.
3. A paraméter neve nem egyezik
string sql = "... WHERE felhnev = @nev";
parancs.Parameters.AddWithValue("@felhasznalo", nev);Hiba és javítás
A paraméter neve az SQL-ben (@nev) és az AddWithValue hívásban (@felhasznalo) különbözik, ezért az SQL-ben szereplő paraméter nem kap értéket. A két névnek pontosan egyeznie kell.
4. Mindig sikeres a belépés
if (DB.BelepesJo(nev, jelszo) == false)
{
Uzenet("Sikeres belépés!", false);
}Hiba és javítás
Fordított feltétel: a == false miatt a sikeres üzenet a hibás belépésnél jelenik meg. Írd így: if (DB.BelepesJo(nev, jelszo)), és a hibás ág legyen az else.
5. Kis- és nagybetű
A Az SQLite az Tanulo névvel nem lehet belépni, pedig tanulo néven van a felhasználó az adatbázisban.Ok és javítás
= összehasonlításnál megkülönbözteti a kis- és nagybetűt. Ha a név esetében ezt nem szeretnéd, írd az SQL-be: felhnev = @nev COLLATE NOCASE. A jelszónál maradjon betűérzékeny.
Mini projekt: biztonságos bejelentkező ablak
Állítsd össze a teljes bejelentkezést:
- Az ablak:
txtFelhasznalo,txtJelszo(rejtett),btnBelep(AcceptButton),lblUzenet. - A
DB.BelepesJometódus paraméterezett lekérdezéssel (a 4. szakasz szerint). - A gomb ellenőrzi az üres mezőket, hívja a metódust, és színes üzenetet ad.
- Próbáld ki: helyes pár, hibás jelszó, ismeretlen név, üres mezők, aposztrófos név, egyetlen aposztróf. Egyik se okozzon összeomlást.
- Zárd be a DB Browsert vagy írd ki a változtatásokat (
Ctrl+S), hogy ne legyen zárolt az adatbázis.
Mit tegyél ide: a kész bejelentkező ablak futás közben, hibás belépés után a piros üzenettel.Javasolt fájlnév: winforms-3-3-kesz.webp · Alt szöveg: A kész bejelentkező ablak hibaüzenettel
Három önálló gyakorlófeladat
Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.
1. Három próbálkozás
Három sikertelen belépés után tiltsd le a Belépés gombot, és írd ki, hogy túl sok sikertelen próbálkozás történt. A Egy lehetséges megoldás
private int sikertelen = 0;
// a hibás ágban (else):
sikertelen = sikertelen + 1;
if (sikertelen >= 3)
{
btnBelep.Enabled = false;
Uzenet("Túl sok sikertelen próbálkozás.", true);
}
else
{
Uzenet("Hibás felhasználónév vagy jelszó.", true);
}sikertelen egy mező (1.12 lecke), amely megjegyzi a próbálkozások számát a kattintások között. A sikeres belépésnél érdemes nullázni.
2. Nagybetű-érzéketlen felhasználónév
Úgy módosítsd a lekérdezést, hogy a felhasználónév nagybetűtől függetlenül egyezzen, a jelszó viszont továbbra is betűérzékeny.Egy lehetséges megoldás
string sql = "SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = @nev COLLATE NOCASE AND jelszo = @jelszo";
3. Üdvözlés a tárolt névvel
A sikeres belépés után az üzenet az adatbázisban tárolt pontos felhasználónevet mutassa (például Üdv, Kata!), nem azt, amit a felhasználó beírt. Találat nélkül az Egy lehetséges megoldás
// a DB osztályban: a név lekérdezése, vagy üres szöveg, ha nincs találat
public static string TaroltNev(string nev, string jelszo)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();
string sql = "SELECT felhnev FROM belepesi_adatok WHERE felhnev = @nev COLLATE NOCASE AND jelszo = @jelszo";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@nev", nev);
parancs.Parameters.AddWithValue("@jelszo", jelszo);
object eredmeny = parancs.ExecuteScalar();
if (eredmeny == null)
{
return "";
}
return eredmeny.ToString();
}
}
}ExecuteScalar() null értéket ad vissza, ezért kell az ellenőrzés. A gombban a DB.TaroltNev(...) eredménye üres szöveg esetén a hibás belépést jelenti.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. A feladatokhoz a 3.3 lecke bejelentkező ablaka kell (txtFelhasznalo, txtJelszo, btnBelep, lblUzenet, DB.BelepesJo). Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Enter a belépéshez
A felhasználó a jelszó beírása után az A Form Enter billentyűvel is beléphessen, ne csak a gombbal. Mit állítasz be?Megoldás megnyitása
AcceptButton tulajdonságát állítsd a btnBelep gombra (Properties ablak, legördülő lista). Az Enter ekkor a gomb Click eseményét hívja, kódot nem kell írnod.
2. Jelszó megjelenítése
Egy A chkMutat jelölőnégyzet kapcsolja a jelszó láthatóságát: bepipálva a jelszó látszik, egyébként pontok.Megoldás megnyitása
UseSystemPasswordChar igaz értéknél rejti a karaktereket, ezért a jelölőnégyzet értékének ellentettjét kell megadni (!).private void chkMutat_CheckedChanged(object sender, EventArgs e)
{
txtJelszo.UseSystemPasswordChar = !chkMutat.Checked;
}
Közepes szint
3. Belépés után
Sikeres belépés után a címsor mutassa: Belépve: kata, a Belépés gomb és a két mező legyen letiltva. Az Megoldás megnyitása
Enabled tulajdonság hamisra állítása letiltja a vezérlőt. A jelszót érdemes a mezőből is törölni, hogy ne maradjon a képernyőn.if (DB.BelepesJo(nev, jelszo))
{
this.Text = "Belépve: " + nev;
Uzenet($"Sikeres belépés, {nev}!", false);
txtJelszo.Clear();
txtFelhasznalo.Enabled = false;
txtJelszo.Enabled = false;
btnBelep.Enabled = false;
}
4. Kijelentkezés
Egy A visszaállítást külön metódusba tesszük, amelyet később máshonnan is hívhatunk. A kurzort a felhasználónév mezőre tesszük.btnKilep gomb állítsa vissza a belépés előtti állapotot: mezők engedélyezve és üresek, címsor az eredeti, üzenet törölve.Megoldás megnyitása
private void btnKilep_Click(object sender, EventArgs e)
{
this.Text = "Bejelentkezés";
lblUzenet.Text = "";
txtFelhasznalo.Clear();
txtJelszo.Clear();
txtFelhasznalo.Enabled = true;
txtJelszo.Enabled = true;
btnBelep.Enabled = true;
txtFelhasznalo.Focus();
}
Kihívás szint
5. Biztonsági ellenőrzőlista
Készíts ötpontos ellenőrzőlistát egy bejelentkező ablak biztonságához, amelyet egy kód átnézésekor végigkérdeznél.Megoldás megnyitása
# Kérdés Jó válasz 1 A felhasználó szövege közvetlenül az SQL-be kerül? Nem: minden érték paraméterként ( @nev) megy át.2 A jelszómező rejtett? Igen: UseSystemPasswordChar bekapcsolva.3 A hibaüzenet elárulja, melyik adat rossz? Nem: ugyanaz az üzenet név- és jelszóhibára. 4 Korlátozott a próbálkozások száma? Igen: néhány hiba után tiltás vagy késleltetés. 5 A jelszót tárolják vagy naplózzák nyílt szövegként? Valódi programban nem: hash-elt tárolás, és a jelszó soha nem kerül naplóba.
6. Belépési napló
Minden belépési kísérlet kerüljön egy Az időt a lstNaplo ListBoxba az idővel és az eredménnyel (14:32:05 – siker: kata, 14:32:40 – sikertelen). A jelszót soha ne írd bele.Megoldás megnyitása
DateTime.Now.ToString("HH:mm:ss") adja. A naplóba csak a felhasználónév és az eredmény kerül, a jelszó soha. Sikertelen kísérletnél a megadott nevet sem naplózzuk, mert a felhasználók néha véletlenül a jelszavukat írják a névmezőbe.private void Naplo(string szoveg)
{
lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " – " + szoveg);
}
// a gombban:
if (DB.BelepesJo(nev, jelszo))
{
Naplo("siker: " + nev);
}
else
{
Naplo("sikertelen");
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Amikor a felhasználó beírt szövege az SQL parancs részévé válik, és megváltoztatja a parancs jelentését. Paraméterezett lekérdezéssel: az SQL szövegében Mert a pontos üzenetből egy támadó megtudná, mely felhasználónevek léteznek. Mert A várt hibát (hibás jelszó) a program az üzenet-felirattal kezeli. A váratlan (adatbázis-)hibát try–catch kapja el, és MessageBox mutatja. Mert ha az adatbázishoz hozzáfér valaki, az összes jelszót elolvashatja. Valódi programban hash-elt jelszót tárolnak.1. Mi az SQL-befecskendezés?
2. Hogyan védekezel ellene?
@ jeles helyfoglalók állnak, az értékeket pedig a Parameters.AddWithValue adja át adatként.3. Miért nem mondjuk meg, hogy a név vagy a jelszó volt-e rossz?
4. Miért kell az ExecuteScalar eredményét átalakítani?
object típusú értéket ad vissza, ezért a Convert.ToInt32 metódussal alakítjuk számmá.5. Mi a különbség a várt és a váratlan hiba kezelése között?
6. Miért nem jó a nyílt jelszótárolás valódi programban?
Mit tudsz már?
- Bejelentkező ablakot készítesz jelszómezővel.
- Érted az SQL-befecskendezést.
- Paraméterezett lekérdezéssel védekezel ellene.
- A műveletet külön, bool értékű metódusba teszed.
- Ismered a bejelentkezés további biztonsági szabályait.
Következő lecke: regisztráció és DataGridView
A bejelentkezés kész. A következő leckében új felhasználót regisztrálunk, és táblázatban megjelenítjük az adatokat.




