10.10. Hallintapaneelin kirjautuminen¶
Web-hallintapaneeli tarvitsee kirjautumislomakkeen – satunnaisten LAN-verkossa olevien ihmisten ei pitäisi nähdä takapihaa. Juuri tätä varten ovat evästepohjaiset istunnot ja kirjautumisdekoraattori.
Istunto on pieni sanakirja, jonka kamera kirjoittaa evästeeseen. Eväste allekirjoitetaan tämän luvun aiemmin lataamalla JWT-allekirjoitussalaisuudella, joten selain voi kuljettaa sitä mukanaan, mutta ei voi peukaloida sisältöä mitätöimättä allekirjoitusta.
10.10.1. Istunto- ja kirjautumisobjektien määrittäminen¶
microdot.session.Session asentaa istuntokoneiston objektiin app. microdot.login.Login lisää login_required-tyylisen dekoraattorin sekä login_user- / logout_user-apufunktiot:
# auth/login.py
from microdot.session import Session
from microdot.login import Login
Session(app, secret_key=SECRET,
cookie_options={'http_only': True, 'secure': False})
login = Login()
USERS = {
'owner': {'id': 'owner', 'password_hash': load_password_hash()},
}
@login.user_loader
async def load_user(user_id):
return USERS.get(user_id)
http_only=True estää sivulla olevaa JavaScriptiä lukemasta evästettä – kerroksellinen suoja XSS-hyökkääjää vastaan, joka yrittäisi kaapata istunnon. secure=False on paikanpitäjä, kunnes HTTPS on käytössä; vaihda se arvoon True, kun palvelin toimii TLS:n yli, jotta eväste ei koskaan kulje pelkän HTTP:n yli.
Muista
Cross-site scripting (XSS) on hyökkäysluokka, jossa hyökkääjä saa JavaScriptin suoritettua luotetun sivun käyttäjälle näkyvän osan sisällä – tyypillisesti escapettamattoman lomakekentän, HTML:nä renderöidyn kommentin tai haavoittuvan kolmannen osapuolen widgetin kautta. Evästeen http_only-lippu ei estä XSS:ää; se vain pitää istuntoevästeen poissa injektoidun skriptin ulottuvilta, joten onnistunutta XSS:ää ei voi triviaalisti muuttaa istunnon kaappaukseksi.
user_loader kutsutaan jokaisella suojatulla pyynnöllä, jotta istuntoon tallennettu tunnus muutetaan käsittelijän näkemäksi käyttäjätietueeksi. Pidä se kevyenä – se ajetaan kuumalla polulla.
10.10.2. Kirjautumislomake, kirjautumisen POST, uloskirjautuminen¶
Itse kirjautumislomake on staattinen sivu, joka tarjotaan kansiosta /sdcard/static/ aivan kuten hallintapaneelikin. Lomake tekee POSTin osoitteeseen /login:
from microdot import redirect
@app.get('/login')
async def login_form(request):
return Response.send_file('/sdcard/static/login.html')
@app.post('/login')
async def do_login(request):
form = request.form
user = USERS.get(form.get('user'))
if not user or not check_password(user, form.get('pass')):
return redirect('/login?error=1')
return await login.login_user(request, user, remember=True)
@app.post('/logout')
async def logout(request):
await login.logout_user(request)
return redirect('/login')
microdot.login.Login.login_user() kirjoittaa istuntoevästeen ja palauttaa 302-uudelleenohjauksen siihen sivuun, jonne asiakas alun perin yritti päästä (next=-kyselyargumentti, oletuksena /). remember=True kirjoittaa myös pidempi-ikäisen _remember-evästeen, jotta istunto säilyy selaimen uudelleenkäynnistyksen yli.
microdot.login.Login.logout_user() tyhjentää molemmat evästeet, ja seuraava uudelleenohjaus lähettää selaimen takaisin lomakkeelle.
10.10.3. Hallintapaneelin suojaaminen¶
Dekoroi jokainen hallintapaneeliin liittyvä reitti dekoraattorilla @login, jotta todentamaton pyyntö saa 302-vastauksen osoitteeseen /login sisällön sijaan:
@app.get('/<path:filename>')
@login
async def static(request, filename):
...
@app.get('/config')
@login
async def get_config(request):
...
@app.post('/config')
@login
async def set_config(request):
...
@app.get('/events')
@login
@with_sse
async def events(request, sse):
...
@app.get('/control')
@login
@with_websocket
async def control(request, ws):
...
Token-pohjaiset API-päätepisteet (/api/login, /api/ack) pysyvät token-pohjaisina – ne ovat puhelinsovellusta varten, eivät selainta varten. Token-todennus ja istuntotodennus toimivat onnellisesti rinnakkain samassa objektissa app.
10.10.4. Tuoreet vs. muistetut istunnot¶
_remember-eväste pitää käyttäjän kirjautuneena selaimen uudelleenkäynnistysten yli, mutta se on heikompi todennusmuoto – selain on voitu jättää kahvilaan. Reitit, jotka vaihtavat salasanoja, rekisteröivät API-tokeneita tai tekevät mitä tahansa muuta uudelleentodennuksen arvoista, kannattaa dekoroida @login-dekoraattorin sijaan dekoraattorilla @login.fresh. Tuore kirjautuminen on sellainen, jossa käyttäjä on kirjoittanut salasanansa tämän istunnon aikana; muistettu kirjautuminen ei ole. Hallintapaneelissa ei ole mitään, mikä nousisi tälle tasolle, mutta dekoraattori on käytettävissä, kun sitä tarvitset.
Hallintapaneeli vaatii nyt kirjautumisen, ennen kuin mikään sen reiteistä vastaa.