10.10. การเข้าสู่ระบบสำหรับแดชบอร์ด

แดชบอร์ดเว็บต้องการฟอร์มเข้าสู่ระบบ -- ไม่ควรให้บุคคลสุ่มบน LAN เห็นเนื้อหาในสวน นั่นคือสิ่งที่เซสชันที่ใช้คุกกี้และ login decorator ทำหน้าที่

เซสชันคือ dict ขนาดเล็กที่ cam เขียนลงในคุกกี้ คุกกี้ถูกเซ็นด้วยความลับสำหรับเซ็น JWT ที่โหลดไว้ก่อนหน้าในบท ดังนั้นเบราว์เซอร์สามารถพกพาไปได้แต่ไม่สามารถแก้ไขเนื้อหาโดยไม่ทำให้ลายเซ็นใช้งานไม่ได้

10.10.1. ตั้งค่าออบเจกต์เซสชันและการเข้าสู่ระบบ

microdot.session.Session ติดตั้งกลไกเซสชันบน app microdot.login.Login เพิ่ม decorator สไตล์ login_required และตัวช่วย 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 ป้องกันไม่ให้ JavaScript บนหน้าอ่านคุกกี้ -- เป็นการป้องกันเป็นชั้นๆ ต่อผู้โจมตี XSS ที่พยายามแฮ็กเซสชัน secure=False เป็นตัวยึดชั่วคราวจนกว่าจะมี HTTPS; เปลี่ยนเป็น True เมื่อเซิร์ฟเวอร์ทำงานผ่าน TLS เพื่อไม่ให้คุกกี้เดินทางผ่าน HTTP แบบธรรมดา

Note

Cross-site scripting (XSS) คือประเภทของการโจมตีที่ผู้โจมตีทำให้ JavaScript ทำงานภายในมุมมองของผู้ใช้บนหน้าที่น่าเชื่อถือ -- โดยทั่วไปผ่านฟิลด์ฟอร์มที่ไม่ได้ escape, ความคิดเห็นที่เรนเดอร์เป็น HTML, หรือวิดเจ็ตของบุคคลที่สามที่มีช่องโหว่ แฟล็กคุกกี้ http_only ไม่ได้ป้องกัน XSS แต่เพียงทำให้คุกกี้เซสชันอยู่นอกเอื้อมมือของสคริปต์ที่ถูกแทรก ดังนั้น XSS ที่สำเร็จจึงไม่สามารถเปลี่ยนเป็นการแฮ็กเซสชันได้ง่ายๆ

user_loader ถูกเรียกในทุกคำขอที่ได้รับการป้องกันเพื่อแปลง ID ที่เก็บในเซสชันเป็นระเบียนผู้ใช้ที่ตัวจัดการจะเห็น ทำให้มีน้ำหนักเบา -- มันทำงานบนเส้นทางหลัก

10.10.2. ฟอร์มเข้าสู่ระบบ, การ POST เข้าสู่ระบบ, การออกจากระบบ

ฟอร์มเข้าสู่ระบบเองเป็นหน้าแบบสแตติกที่ให้บริการจาก /sdcard/static/ เช่นเดียวกับแดชบอร์ด ฟอร์ม POSTไปยัง /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() เขียนคุกกี้เซสชันและส่งคืนการเปลี่ยนเส้นทาง 302 ไปยังหน้าที่ไคลเอนต์พยายามเข้าถึงในตอนแรก (อาร์กิวเมนต์ query next= โดยใช้ / เป็นค่าเริ่มต้น) remember=True ยังเขียนคุกกี้ _remember ที่มีอายุยาวนานกว่าเพื่อให้เซสชันอยู่รอดหลังจากรีสตาร์ทเบราว์เซอร์

microdot.login.Login.logout_user() ล้างทั้งสองคุกกี้และการเปลี่ยนเส้นทางติดตามส่งเบราว์เซอร์กลับไปที่ฟอร์ม

10.10.3. การป้องกันแดชบอร์ด

ตกแต่งทุก route ที่หันหน้าสู่แดชบอร์ดด้วย @login เพื่อให้คำขอที่ไม่ได้รับการพิสูจน์ตัวตนได้รับการเปลี่ยนเส้นทาง 302 ไปยัง /login แทนที่จะเป็นเนื้อหา:

@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):
    ...

endpoint ของ API แบบใช้โทเค็น (/api/login, /api/ack) ยังคงใช้โทเค็น -- สำหรับแอปโทรศัพท์ ไม่ใช่เบราว์เซอร์ การพิสูจน์ตัวตนด้วยโทเค็นและการพิสูจน์ตัวตนด้วยเซสชันอยู่ร่วมกันได้บน app เดียวกัน

10.10.4. เซสชันใหม่ vs เซสชันที่จดจำ

คุกกี้ _remember ทำให้ผู้ใช้ยังคงเข้าสู่ระบบได้ข้ามการรีสตาร์ทเบราว์เซอร์ แต่เป็นรูปแบบการพิสูจน์ตัวตนที่อ่อนแอกว่า -- เบราว์เซอร์อาจถูกทิ้งไว้ที่ร้านกาแฟ สำหรับ route ที่เปลี่ยนรหัสผ่าน, ลงทะเบียน API token, หรือทำสิ่งอื่นใดที่คุ้มค่าแก่การพิสูจน์ตัวตนใหม่ ให้ตกแต่งด้วย @login.fresh แทน @login การเข้าสู่ระบบใหม่คือเมื่อผู้ใช้พิมพ์รหัสผ่านในเซสชัน นี้; การเข้าสู่ระบบที่จดจำไม่ใช่ แดชบอร์ดไม่มีอะไรที่สูงถึงระดับนั้น แต่ decorator อยู่ที่นั่นเมื่อคุณต้องการ

ขณะนี้แดชบอร์ดต้องการการเข้าสู่ระบบก่อนที่ route ใดๆ จะตอบสนอง