10.10. Prijava za nadzornu ploču¶
Web nadzorna ploča treba obrazac za prijavu – slučajni ljudi na LAN-u ne bi trebali vidjeti dvorište. Upravo to čine sesije podržane kolačićima i dekorator za prijavu.
Sesija je mali rječnik koji kamera upisuje u kolačić. Kolačić je potpisan tajnim ključem za potpisivanje JWT-a koji je učitan ranije u poglavlju, pa ga preglednik može nositi sa sobom, ali ne može mijenjati njegov sadržaj bez poništavanja potpisa.
10.10.1. Postavljanje objekata sesije i prijave¶
microdot.session.Session instalira mehanizam sesija na app. microdot.login.Login dodaje dekorator u stilu login_required te pomoćne funkcije login_user / logout_user:
# 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 sprječava da JavaScript na stranici pročita kolačić – slojevita obrana protiv XSS napadača koji bi oteo sesiju. secure=False je rezervirano mjesto dok se ne uvede HTTPS; prebacite ga na True kada poslužitelj radi preko TLS-a kako kolačić nikada ne bi putovao preko običnog HTTP-a.
Napomena
Cross-site scripting (XSS) je vrsta napada u kojoj napadač uspijeva izvršiti JavaScript unutar korisnikova pogleda na pouzdanu stranicu – obično putem neeskepiranog polja obrasca, komentara prikazanog kao HTML ili ranjivog widgeta treće strane. Zastavica kolačića http_only ne sprječava XSS; ona samo drži kolačić sesije izvan dosega ubačene skripte, pa se uspješan XSS ne može trivijalno pretvoriti u otmicu sesije.
user_loader poziva se na svaki zaštićeni zahtjev kako bi ID pohranjen u sesiji pretvorio u zapis korisnika koji će rukovatelj vidjeti. Neka bude jeftin – izvršava se na vrućoj putanji.
10.10.2. Obrazac za prijavu, slanje prijave, odjava¶
Sam obrazac za prijavu statična je stranica koja se poslužuje iz /sdcard/static/ baš kao i nadzorna ploča. Obrazac šalje POSTna /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() upisuje kolačić sesije i vraća preusmjeravanje 302 na onu stranicu koju je klijent izvorno pokušao doseći (argument upita next=, uz povratak na /). remember=True također upisuje dugotrajniji kolačić _remember kako bi sesija preživjela ponovno pokretanje preglednika.
microdot.login.Login.logout_user() briše oba kolačića, a naknadno preusmjeravanje vraća preglednik natrag na obrazac.
10.10.3. Zaštita nadzorne ploče¶
Dekorirajte svaku rutu okrenutu prema nadzornoj ploči s @login kako bi neautentificirani zahtjev dobio 302 na /login umjesto sadržaja:
@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):
...
Krajnje točke API-ja temeljene na tokenima (/api/login, /api/ack) ostaju temeljene na tokenima – one su za aplikaciju na telefonu, a ne za preglednik. Autentifikacija tokenom i autentifikacija sesijom skladno supostoje na istom app.
10.10.4. Svježe nasuprot zapamćenim sesijama¶
Kolačić _remember drži korisnika prijavljenim kroz ponovna pokretanja preglednika, ali to je slabiji oblik autentifikacije – preglednik je mogao ostati u kafiću. Za rute koje mijenjaju lozinke, registriraju API tokene ili rade bilo što drugo zbog čega se vrijedi ponovno autentificirati, dekorirajte s @login.fresh umjesto s @login. Svježa prijava je ona u kojoj je korisnik upisao svoju lozinku u ovoj sesiji; zapamćena prijava nije. Nadzorna ploča nema ništa što doseže tu razinu, ali dekorator je tu kada ga zatrebate.
Nadzorna ploča sada zahtijeva prijavu prije nego što bilo koja od njezinih ruta odgovori.