10.10. Đăng nhập cho bảng điều khiển¶
Bảng điều khiển web cần một biểu mẫu đăng nhập -- những người lạ trên LAN không nên thấy được khu vực này. Đó chính là những gì session được hỗ trợ bởi cookie và decorator đăng nhập thực hiện.
Session là một dict nhỏ mà camera ghi vào cookie. Cookie được ký bằng khóa bí mật JWT được tải trước đó trong chương, vì vậy trình duyệt có thể mang nó theo nhưng không thể giả mạo nội dung mà không làm mất hiệu lực chữ ký.
10.10.1. Thiết lập đối tượng session và đăng nhập¶
microdot.session.Session cài đặt cơ chế session cho app. microdot.login.Login thêm decorator kiểu login_required và các hàm trợ giúp 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 ngăn JavaScript trên trang đọc cookie -- một lớp bảo vệ chống lại kẻ tấn công XSS chiếm đoạt session. secure=False là giá trị tạm thời cho đến khi HTTPS được thiết lập; hãy đổi thành True sau khi máy chủ chạy trên TLS để cookie không bao giờ truyền qua HTTP thuần.
Ghi chú
Cross-site scripting (XSS) là loại tấn công mà kẻ tấn công khiến JavaScript thực thi bên trong chế độ xem của người dùng trên một trang đáng tin cậy -- thường thông qua một trường biểu mẫu không được thoát, một nhận xét được render HTML, hoặc một widget bên thứ ba có lỗ hổng. Cờ cookie http_only không ngăn chặn XSS; nó chỉ giữ cho cookie session nằm ngoài tầm với của script được chèn vào, để một cuộc tấn công XSS thành công không thể dễ dàng trở thành chiếm đoạt session.
user_loader được gọi trên mỗi yêu cầu được bảo vệ để chuyển đổi ID được lưu trong session thành bản ghi người dùng mà handler sẽ thấy. Hãy giữ nó đơn giản -- nó chạy trên đường dẫn nóng.
10.10.2. Biểu mẫu đăng nhập, POST đăng nhập, đăng xuất¶
Bản thân biểu mẫu đăng nhập là một trang tĩnh được phục vụ từ /sdcard/static/ giống như bảng điều khiển. Biểu mẫu POSTs đến /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() ghi cookie session và trả về chuyển hướng 302 đến trang mà client ban đầu cố gắng truy cập (tham số truy vấn next=, mặc định là /). remember=True cũng ghi thêm cookie _remember có thời hạn dài hơn để session tồn tại sau khi khởi động lại trình duyệt.
microdot.login.Login.logout_user() xóa cả hai cookie và chuyển hướng tiếp theo đưa trình duyệt quay lại biểu mẫu.
10.10.3. Bảo vệ bảng điều khiển¶
Trang trí mỗi route hướng tới bảng điều khiển bằng @login để yêu cầu chưa xác thực nhận được chuyển hướng 302 đến /login thay vì nội dung:
@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):
...
Các endpoint API dựa trên token (/api/login, /api/ack) vẫn dựa trên token -- chúng dành cho ứng dụng điện thoại, không phải trình duyệt. Xác thực bằng token và xác thực bằng session cùng tồn tại trên cùng một app.
10.10.4. Session mới so với session được ghi nhớ¶
Cookie _remember giữ cho người dùng đăng nhập qua các lần khởi động lại trình duyệt, nhưng đây là hình thức xác thực yếu hơn -- trình duyệt có thể đã bị bỏ lại trong một quán cà phê. Đối với các route thay đổi mật khẩu, đăng ký token API, hoặc bất kỳ thao tác nào khác đáng để xác thực lại, hãy trang trí bằng @login.fresh thay vì @login. Đăng nhập mới là khi người dùng gõ mật khẩu của họ trong session này; đăng nhập được ghi nhớ thì không. Bảng điều khiển không có gì đạt đến ngưỡng đó, nhưng decorator vẫn ở đó khi bạn cần nó.
Bảng điều khiển hiện yêu cầu đăng nhập trước khi bất kỳ route nào của nó phản hồi.