10.10. Autentificare pentru tabloul de bord¶
Tabloul de bord web are nevoie de un formular de autentificare – persoane oarecare din rețeaua LAN nu ar trebui să vadă curtea. Exact acest lucru îl fac sesiunile bazate pe cookie-uri și decoratorul de autentificare.
O sesiune este un mic dict pe care camera îl scrie într-un cookie. Cookie-ul este semnat cu secretul de semnare JWT încărcat mai devreme în acest capitol, astfel încât browserul îl poate purta cu el, dar nu poate modifica conținutul fără a invalida semnătura.
10.10.1. Configurarea obiectelor de sesiune și de autentificare¶
microdot.session.Session instalează mecanismul de sesiune pe app. microdot.login.Login adaugă decoratorul de tip login_required și funcțiile ajutătoare 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 împiedică JavaScript-ul din pagină să citească cookie-ul – o apărare stratificată împotriva unui atacator XSS care încearcă să deturneze sesiunea. secure=False este un substituent temporar până când HTTPS este implementat; comutați-l la True odată ce serverul rulează peste TLS, astfel încât cookie-ul să nu circule niciodată prin HTTP simplu.
Notă
Cross-site scripting (XSS) este categoria de atacuri în care atacatorul reușește să execute JavaScript în vizualizarea utilizatorului asupra unei pagini de încredere – de obicei printr-un câmp de formular neescapat, un comentariu redat ca HTML sau un widget terț vulnerabil. Indicatorul de cookie http_only nu previne XSS; el doar ține cookie-ul de sesiune în afara accesului scriptului injectat, astfel încât un atac XSS reușit să nu poată fi transformat trivial în deturnarea sesiunii.
user_loader este apelat la fiecare cerere protejată pentru a transforma ID-ul stocat în sesiune în înregistrarea de utilizator pe care o va vedea handlerul. Păstrați-l ieftin – rulează pe calea critică.
10.10.2. Formular de autentificare, post de autentificare, deconectare¶
Formularul de autentificare în sine este o pagină statică servită din /sdcard/static/, la fel ca tabloul de bord. Formularul face POST către /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() scrie cookie-ul de sesiune și returnează o redirecționare 302 către pagina pe care clientul a încercat inițial să o acceseze (argumentul de interogare next=, cu revenire la /). remember=True scrie de asemenea un cookie _remember cu durată de viață mai lungă, astfel încât sesiunea să supraviețuiască unei reporniri a browserului.
microdot.login.Login.logout_user() șterge ambele cookie-uri, iar o redirecționare ulterioară trimite browserul înapoi la formular.
10.10.3. Protejarea tabloului de bord¶
Decorați fiecare rută destinată tabloului de bord cu @login, astfel încât o cerere neautentificată să primească un 302 către /login în loc de conținut:
@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):
...
Punctele finale ale API-ului bazate pe token (/api/login, /api/ack) rămân bazate pe token – sunt destinate aplicației de telefon, nu browserului. Autentificarea pe bază de token și autentificarea pe bază de sesiune coexistă fără probleme pe aceeași aplicație app.
10.10.4. Sesiuni proaspete vs. memorate¶
Cookie-ul _remember păstrează utilizatorul autentificat între reporniri ale browserului, dar este o formă mai slabă de autentificare – browserul ar fi putut fi lăsat într-o cafenea. Pentru rutele care schimbă parole, înregistrează token-uri API sau fac orice altceva care merită reautentificat, decorați cu @login.fresh în loc de @login. O autentificare proaspătă este una în care utilizatorul și-a introdus parola în această sesiune; o autentificare memorată nu este. Tabloul de bord nu are nimic care să ajungă la acel nivel, dar decoratorul este disponibil atunci când aveți nevoie de el.
Tabloul de bord necesită acum o autentificare înainte ca oricare dintre rutele sale să răspundă.