10.9. Hitelesítés programozott klienseknek¶
A bearer tokenek azok, amelyekkel a telefonok, a kísérő mikrovezérlők és a felhőbeli munkavégzők hitelesítik magukat egy szerver felé – a kliens minden kérésben elhelyez egy tokent az Authorization fejlécben, a szerver pedig ellenőrzi azt. A kísérő telefonos alkalmazás egy „ack” üzenetet POST-ol, amikor a tulajdonos egy mozgásértesítésnél a „látott” gombra koppint; ezek a kérések tokent igényelnek.
10.9.1. Az aláíró titok¶
A titok a kamera privát kulcsa a tokenek aláírásához és ellenőrzéséhez. A kamera első indításakor generálj 32 véletlenszerű bájtot a hardveres RNG-ből, tartsd meg azokat a fájlrendszerben, és minden későbbi indításkor használd újra ugyanazokat a bájtokat:
# auth/tokens.py
import os
try:
with open('secret.bin', 'rb') as f:
SECRET = f.read()
except OSError:
SECRET = os.urandom(32)
with open('secret.bin', 'wb') as f:
f.write(SECRET)
Az os.urandom() a kriptográfiailag megfelelő véletlenforrás minden kamera porton – a legtöbb porton közvetlenül a chip hardveres véletlenszám-generátorát olvassa. A fájl a kamera munkakönyvtárában található (/sdcard, ha SD-kártya van csatlakoztatva, egyébként /flash), és soha nem hagyja el a kamerát. A secret.bin törlése és az újraindítás lecseréli a titkot, és érvényteleníti a régi alatt kiadott összes tokent.
Megjegyzés
A machine.unique_id() nem helyettesíti ezt. Egyes kamera portokon a hálózati MAC-cím származtatására is használják, ami azt jelenti, hogy az értéke minden csomaggal együtt utazik, amelyet a kamera küld – nem ez az a tulajdonság, amelyre egy aláíró titoknak szüksége van.
10.9.2. Tokenek kiadása¶
A telefonnak először is szüksége van valamilyen módra, hogy megszerezzen egy tokent. Egy rövid élettartamú JSON Web Token (JWT) – egy base64-kódolt JSON hasznos teher hozzáfűzött aláírással – elegendő egy első megközelítéshez: a tulajdonos jelszót cserél tokenre, majd a tokent használja, amíg le nem jár:
import jwt
import time
@app.post('/api/login')
async def api_login(request):
creds = request.json
if creds.get('user') != 'owner' or creds.get('pass') != load_password():
abort(401)
token = jwt.encode({
'sub': 'owner',
'exp': int(time.time()) + 3600,
}, SECRET)
return {'token': token}
A telefon egyszer POST-olja a {user, pass} adatokat, eltárolja a visszakapott tokent a helyi tárolóban, és minden további kérésben Authorization: Bearer <token> formában mellékeli.
Az exp állítás (claim) egy Unix időbélyeg. A következő szakaszban szereplő ellenőrző erre az állításra támaszkodik, hogy automatikusan elutasítsa a lejárt tokeneket – ami azt jelenti, hogy a kamera valós idejű óráját be kell állítani, mert egyébként minden token vagy távol a jövőben, vagy már lejártnak fog tűnni. Az NTP-szinkronizálás receptjéhez lásd: Idő és NTP.
10.9.3. Tokenek ellenőrzése a TokenAuth segítségével¶
A microdot.auth.TokenAuth a dekorátorgyár olyan útvonalakhoz, amelyek Authorization: Bearer <token> fejlécet igényelnek. Az ellenőrző visszahívás megkapja a token sztringet, és visszaadja azt az identitásobjektumot, amelyet a kéréshez kell csatolni – vagy None értéket az elutasításhoz:
from microdot.auth import TokenAuth
tokens = TokenAuth()
@tokens.authenticate
async def check_token(request, token):
try:
claims = jwt.decode(token, SECRET)
except jwt.exceptions.PyJWTError:
return None
return claims['sub']
@app.post('/api/ack')
@tokens
async def ack(request):
body = request.json
if not body or 'count' not in body:
abort(400, 'missing count')
return {'ok': True, 'user': request.g.current_user}
A jwt.decode() jwt.exceptions.PyJWTError kivételt (vagy egy alosztályát) vált ki, ha az aláírás hibás, a token hibásan formázott, vagy az exp állítás már lejárt. Az ellenőrző mindezeket lenyeli, és None értéket ad vissza, így a microdot 401-gyel válaszol anélkül, hogy a kezelő bármit is látna.
Amikor az ellenőrző nem None értéket ad vissza, a microdot eltárolja azt a request.g.current_user mezőben, hogy a kezelő kiolvashassa – ebben az esetben ez a JWT sub (subject) állítása.
10.9.4. BasicAuth zárt hálózatokhoz¶
Egy olyan adminisztrációs végponthoz, amelyet csak megbízható hálózatról érnek el, a microdot.auth.BasicAuth egyszerűbb – a böngésző egy natív felhasználónév/jelszó párbeszédablakot dob fel, és minden kérésben elküldi azokat az Authorization: Basic ... fejlécben:
from microdot.auth import BasicAuth
basic = BasicAuth(realm='Backyard cam admin')
@basic.authenticate
async def check_basic(request, username, password):
if username == 'owner' and password == load_password():
return 'owner'
return None
@app.get('/admin')
@basic
async def admin(request):
return 'hi ' + request.g.current_user
A hitelesítő adatok nyíltan utaznak, hacsak a kapcsolat nem HTTPS, így ennek a megközelítésnek csak megbízható hálózaton vagy HTTPS megléte esetén van értelme.
Az /api/ack és az /api/login mostantól érti a tokeneket; az /admin Basic hitelesítést igényel.