Låsskärmen
Bygg låset
Nästan varje app du använder börjar med samma sak: en ruta som vill ha ett lösenord innan resten släpps fram. Nu ska vi bygga det mönstret med delar du redan kan: ett textfält, en knapp och en strängjämförelse.
I Fönsterhanteraren och Knappar & räknaren byggde du fönster med knappar och etiketter som reagerar på ett tillstånd. En inloggning är samma mönster med ett nytt tillstånd: upplåst eller inte.
En viktig varning dock, så att du inte behöver undra: versionen du bygger lagrar lösenordet i klartext, mitt i koden. Det är precis det du i Personuppgifter & integritet fick veta att man aldrig ska göra. Det är avsiktligt, och vi återkommer till varför nedan.
Fältet och kontrollen
För att ta emot ett lösenord behöver du ett nytt slags widget. tk.Entry() är ett textfält, input() i grafisk form, men det stannar inte programmet på samma sätt. Fältet står och väntar, och du hämtar det som skrivits när du behöver det, med .get(), som alltid ger dig en sträng. Ett lösenordsfält får dessutom show="*", som gör att varje tecken visas som en stjärna på skärmen.
import tkinter as tk
RÄTT_LÖSENORD = "drake123"
def kontrollera():
försök = lösenord_fält.get().strip()
if försök == RÄTT_LÖSENORD:
status.config(text="Upplåst!", fg="green")
else:
status.config(text="Fel lösenord.", fg="red")
lösenord_fält.delete(0, tk.END)
root = tk.Tk()
root.title("Lås")
root.geometry("300x180")
lösenord_fält = tk.Entry(root, show="*")
lösenord_fält.pack(pady=20)
lås_knapp = tk.Button(root, text="Lås upp", command=kontrollera)
lås_knapp.pack()
status = tk.Label(root, text="", font=("Arial", 14))
status.pack(pady=20)
root.mainloop() lösenord_fält.get() hämtar det användaren skrev, .strip() städar bort mellanslag i kanterna, och jämförelsen avgör. Rätt lösenord ger grön text, fel ger röd text och ett tömt fält.
Motsägelsen på rad 3
Titta på RÄTT_LÖSENORD = "drake123". Där står lösenordet, i klartext, i en fil du strax ska pusha till ett publikt repo.
Det var precis så du inte skulle göra. Och det gör vi ändå, för att funktionen ska komma på plats innan säkerheten gör det.
Det finns ett namn på sådana här medvetna genvägar: Teknisk skuld En medveten genväg i koden som fungerar nu men gör framtida arbete dyrare. Precis som ett lån är den ibland värd att ta, den låter dig komma vidare i dag mot att någon får betala i morgon. Det farliga är inte att ta lånet, utan att glömma bort att man gjort det. . Precis som ett lån är den ibland värd att ta, den låter dig komma vidare i dag mot att någon får betala i morgon. Det farliga är inte att ta lånet, det är att glömma bort att man gjort det. Skriv därför in det i din README: låset lagrar lösenordet i klartext, och det ska bytas.
Så var tydlig med vad du byggt. Det här är ett lås i betydelsen “en dörr som är stängd”, inte i betydelsen “en dörr som håller”. Det stoppar någon som inte tänkt på saken. Det stoppar ingen som öppnar din kodfil, och det är en väldigt låg ribba, för koden ligger öppet på GitHub.
Fixen finns, den är kort, och den ligger som utmaning sist i delen. Vill du att låset faktiskt ska betyda något är det dit du ska.
Lägg märke till en sak som saknas: det finns ingen loop. I Kraschsäkring byggde du Valideraren med while True, en loop som frågade om och om igen tills svaret dög. Här finns ingen sådan, och det ska inte finnas. kontrollera() körs en gång per klick, kollar, och uppdaterar etiketten. Blev det fel är användarens nästa klick själva omförsöket. Det är samma skifte som i Fönsterhanteraren: mainloop är väntan, och väntan ersätter loopen.
Strängmetoderna du behöver här
I Vägskälet mötte du .lower() och fick veta att det fanns fler strängmetoder. Här behöver du dem, för användarinmatning är sällan så prydlig som du hoppas.
| Metod | Gör | Exempel |
|---|---|---|
.strip() | tar bort mellanslag i början och slutet | " drake ".strip() blir "drake" |
.lower() | gör om till gemener | "Drake".lower() blir "drake" |
.upper() | gör om till versaler | "drake".upper() blir "DRAKE" |
.replace() | byter ut ett tecken eller en delsträng | "a-b".replace("-", "") blir "ab" |
.isdigit() | svarar True om allt är siffror | "123".isdigit() blir True |
Ingen av dem ändrar originalet, alla returnerar en ny sträng. Det är samma regel som gällde för int() och str(): resultatet måste tas emot och sparas i en varibel, annars försvinner det.
.strip() är den som räddar flest inloggningar. Klistrar användaren in sitt lösenord följer ofta ett mellanslag med, och "drake123 " är inte samma sträng som "drake123". Felet är osynligt på skärmen, och användaren är övertygad om att hen skrivit rätt.
Ska lösenordet dessutom inte använda stora eller små bokstäver kedjar du dem: .strip().lower(). Metoderna körs från vänster till höger, så den första städar och den andra ändrar till gemener.
Visa appen först efter upplåsning
Ett riktigt lås gör mer än att skriva “Upplåst!“. När lösenordet stämmer ska inloggningen försvinna och appen träda fram. Motsatsen till .pack() heter .pack_forget(), och den plockar bort en widget ur fönstret utan att förstöra den:
lösenord_fält.pack_forget()
lås_knapp.pack_forget()
app_vy.pack(pady=20) Tre rader, och de hör hemma i else-grenens motsats, alltså där du i dag skriver “Upplåst!“.
Men för att kunna gömma en widget måste du ha sparat den i en variabel, annars finns det inget att gömma. Och där väntar en fälla. Skriver du lås_knapp = tk.Button(...).pack() hamnar inte knappen i variabeln, det gör None. Det är samma sak som i Funktionen svarar: .pack() lämnar inte tillbaka något, precis som en funktion utan return.
Vill du kunna nå widgeten senare skapar du den på en rad och packar den på nästa:
lås_knapp = tk.Button(root, text="Lås upp", command=kontrollera)
lås_knapp.pack() Det förklarar varför vissa widgetar i exemplet ovan sparas i variabler och andra inte. Den som aldrig ska ändras eller gömmas behöver du aldrig komma åt igen, och då räcker det att skapa och packa den i ett svep.
Resten är strukturen du redan kan från Knappar & räknaren, en uppdatera_gui() som läser tillståndet och ritar rätt vy. Här är tillståndet bara upplåst, sant eller falskt.
Uppgift: Lås din app
Bygg i VS Code.
Ta ett av dina GUI-program, eller starta ett litet nytt fönster, och bygg en inloggningsvy: ett
Entrymedshow="*", en “Lås upp”-knapp och en status-etikett.Knyt knappen till en
kontrollera()-funktion som läser fältet och jämför. Rätt lösenord låser upp, fel lösenord ger tydlig återkoppling och tömmer fältet.Städa inmatningen med
.strip()innan du jämför. Testa sedan att skriva lösenordet med ett mellanslag efter, det ska fungera ändå.Stagea filen, skriv ett commit-meddelande och pusha.
Vad händer om
Du tar bort .strip() och klistrar in lösenordet med ett mellanslag på slutet. Vad händer, och hur lätt hade det felet varit att förstå om du var användaren och inte utvecklaren?
Motivera & reflektera
Skriv i din README.md: din låsskärm ser säker ut, men hur säker är den egentligen om koden ligger i ett publikt repo? Vad skulle behövas för att göra den på riktigt?
Nästa nivå
Lös motsägelsen. Problemet med koden ovan är att lösenordet står där, läsbart. Lösningen som riktiga system använder är att lagra ett oåterkalleligt avtryck i stället, en Hash Ett oåterkalleligt avtryck av en text. Samma text ger alltid samma avtryck, men avtrycket går inte att räkna tillbaka till texten. Det är därför riktiga system lagrar avtrycket i stället för lösenordet. . Samma text ger alltid samma avtryck, men det går inte att räkna baklänges, och därför kan avtrycket ligga öppet i koden utan att avslöja något.
Python har det inbyggt i modulen hashlib:
import hashlib
avtryck = hashlib.sha256("drake123".encode()).hexdigest()
print(avtryck) Kör raden och se de 64 tecknen. Byt en enda bokstav i lösenordet och kör igen, hela avtrycket blir ett annat.
Bygg om ditt lås så att koden bara innehåller avtrycket, aldrig lösenordet. Kontrollen blir då att räkna fram avtrycket av det användaren skrev och jämföra de två avtrycken. Dokumentationen för hashlib är på engelska men kort, .encode() behövs för att hashfunktioner arbetar på bytes och inte på text.
Begränsa antalet försök. En Räknare från Mönsterkortet håller reda på hur många gånger någon gissat fel, och vid tre fel inaktiverar du knappen. Varför är en sådan gräns ett helt annat sorts skydd än ett längre lösenord?