10.10. Inloggning för instrumentpanelen¶
Webbinstrumentpanelen behöver ett inloggningsformulär – slumpmässiga personer på LAN:et ska inte kunna se trädgården. Det är det cookie-baserade sessioner och inloggningsdekoratorn gör.
En session är en liten dict som kameran skriver in i en cookie. Cookien signeras med JWT-signeringshemligheten som laddades tidigare i kapitlet, så webbläsaren kan bära runt på den men kan inte manipulera innehållet utan att ogiltigförklara signaturen.
10.10.1. Konfigurera session- och login-objekten¶
microdot.session.Session installerar sessionsmaskineriet på app. microdot.login.Login lägger till dekoratorn i login_required-stil samt hjälpfunktionerna 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 hindrar JavaScript på sidan från att läsa cookien – ett lager av försvar mot att en XSS-angripare kapar sessionen. secure=False är en platshållare tills HTTPS finns på plats; ändra den till True när servern körs över TLS så att cookien aldrig färdas över oskyddad HTTP.
Anteckning
Cross-site scripting (XSS) är den klass av attacker där angriparen får JavaScript att köras inuti användarens vy av en betrodd sida – typiskt via ett oescapat formulärfält, en HTML-renderad kommentar eller en sårbar tredjepartswidget. Cookie-flaggan http_only förhindrar inte XSS; den håller bara sessionscookien utom räckhåll för det injicerade skriptet, så att en lyckad XSS inte enkelt kan omvandlas till sessionskapning.
user_loader anropas vid varje skyddad begäran för att omvandla det ID som lagrats i sessionen till den användarpost som hanteraren får se. Håll den billig – den körs på den heta vägen.
10.10.2. Inloggningsformulär, login-post, utloggning¶
Själva inloggningsformuläret är en statisk sida som serveras från /sdcard/static/ precis som instrumentpanelen. Formuläret gör POST till /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() skriver sessionscookien och returnerar en 302-omdirigering till vilken sida klienten ursprungligen försökte nå (frågeargumentet next=, med fallback till /). remember=True skriver dessutom en mer långlivad _remember-cookie så att sessionen överlever en omstart av webbläsaren.
microdot.login.Login.logout_user() rensar båda cookies och en efterföljande omdirigering skickar webbläsaren tillbaka till formuläret.
10.10.3. Skydda instrumentpanelen¶
Dekorera varje instrumentpanelsvänd rutt med @login så att en oautentiserad begäran får en 302 till /login i stället för innehållet:
@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):
...
De token-baserade API-slutpunkterna (/api/login, /api/ack) förblir token-baserade – de är till för telefonappen, inte webbläsaren. Token-autentisering och sessionsautentisering samexisterar gärna på samma app.
10.10.4. Färska kontra ihågkomna sessioner¶
_remember-cookien håller användaren inloggad mellan omstarter av webbläsaren, men det är en svagare form av autentisering – webbläsaren kan ha lämnats kvar på ett café. För rutter som ändrar lösenord, registrerar API-tokens eller gör något annat som är värt att autentisera om för, dekorera med @login.fresh i stället för @login. En färsk inloggning är en där användaren skrev sitt lösenord denna session; en ihågkommen inloggning är det inte. Instrumentpanelen har inget som når upp till den nivån, men dekoratorn finns där när du behöver den.
Instrumentpanelen kräver nu en inloggning innan någon av dess rutter svarar.