Security - Bảo vệ Jarvis bằng "Lá chắn 2 lớp" (Auth & API Key)

AI Hunter

Member
Hiện tại, hệ thống của chúng ta đang giống như một ngôi nhà không khóa cửa. Ai biết IP là vào được.
Hôm nay, chúng ta sẽ lắp cửa sắt và khóa số.

Security - Bảo vệ Jarvis bằng Lá chắn 2 lớp (Auth & API Key).jpg

1. Chiến lược bảo mật​


Chúng ta sẽ áp dụng mô hình Microservice Security:

  • Frontend (Chainlit): Đóng vai trò là "Cổng chính". Người dùng (User) phải có Username/Password mới được vào.
  • Backend (FastAPI): Đóng vai trò là "Két sắt". Nó không quan tâm User là ai, nó chỉ quan tâm người gọi nó có cầm "Chìa khóa vạn năng" (API Secret Key) hay không.

Chainlit sẽ giữ chiếc chìa khóa này và thay mặt User gọi xuống Backend.

2. Bước 1: Khóa Backend (FastAPI)​


Chúng ta sẽ dùng cơ chế Bearer Token đơn giản (một dạng Static Token) để bảo vệ API.

Mở file backend/server.py và thêm "ổ khóa":

Python:
from fastapi import FastAPI, HTTPException, Depends, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
import os

app = FastAPI()
security = HTTPBearer()

# Lấy mật khẩu API từ biến môi trường (Docker sẽ truyền vào)
JARVIS_API_SECRET = os.getenv("JARVIS_API_SECRET", "mac-dinh-khong-an-toan")

# --- HÀM BẢO VỆ (GARDEN GUARD) ---
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
    token = credentials.credentials
    if token != JARVIS_API_SECRET:
        raise HTTPException(
            status_code=403,
            detail="⛔ Truy cập bị từ chối! Sai mật khẩu API."
        )
    return token

class ChatRequest(BaseModel):
    message: str

# Thêm dependencies=[Depends(verify_token)] để khóa endpoint này lại
@app.post("/chat", dependencies=[Depends(verify_token)])
async def chat_endpoint(req: ChatRequest):
    # Logic xử lý cũ giữ nguyên
    return {"answer": f"Xin chào, tôi đã nhận lệnh: {req.message}"}

Từ giờ, nếu ai gọi vào /chat mà không có Header Authorization: Bearer <JARVIS_API_SECRET>, họ sẽ bị đá ra ngay lập tức (Lỗi 403).

3. Bước 2: Khóa Frontend (Chainlit)​


Chainlit hỗ trợ sẵn tính năng đăng nhập cực xịn.
Mở file frontend/app.py:

Python:
import chainlit as cl
import requests
import os

API_URL = os.getenv("API_URL", "http://localhost:8000/chat")
# Lấy chìa khóa để lát nữa gửi cho Backend
BACKEND_KEY = os.getenv("JARVIS_API_SECRET", "mac-dinh-khong-an-toan")

# 1. Cấu hình xác thực User (Login)
@cl.password_auth_callback
def auth_callback(username, password):
    # Ở đây mình hardcode demo. Thực tế nên check từ DB hoặc biến môi trường.
    # User: admin, Pass: jarvis123
    if username == "admin" and password == "jarvis123":
        return cl.User(identifier=username, metadata={"role": "admin"})
    return None # Đăng nhập thất bại

@cl.on_chat_start
async def start():
    await cl.Message(content="🔒 Hệ thống đã được bảo mật. Chào mừng sếp quay lại!").send()

@cl.on_message
async def main(message: cl.Message):
    # Chuẩn bị Header chứa chìa khóa
    headers = {
        "Authorization": f"Bearer {BACKEND_KEY}",
        "Content-Type": "application/json"
    }
   
    payload = {"message": message.content}

    msg = cl.Message(content="")
    await msg.send()
   
    try:
        # Gửi request kèm Header xác thực
        response = requests.post(API_URL, json=payload, headers=headers)
       
        if response.status_code == 200:
            data = response.json()
            msg.content = data.get("answer", "")
        elif response.status_code == 403:
            msg.content = "⛔ Lỗi bảo mật: Sai API Key kết nối Backend!"
        else:
            msg.content = f"Lỗi Server: {response.text}"
           
        await msg.update()
           
    except Exception as e:
        msg.content = f"Lỗi kết nối: {str(e)}"
        await msg.update()

4. Bước 3: Cập nhật Docker Compose​


Bây giờ chúng ta cần cung cấp "mật khẩu chung" cho cả 2 container qua biến môi trường.
Sửa file docker-compose.yml:

Mã:
version: '3.8'

services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - JARVIS_API_SECRET=sieumatkhau123456 # <--- Mật khẩu Backend
      # ... các biến khác giữ nguyên

  frontend:
    build: ./frontend
    ports:
      - "8501:8000"
    environment:
      - API_URL=http://backend:8000/chat
      - JARVIS_API_SECRET=sieumatkhau123456 # <--- Phải trùng với Backend
      - CHAINLIT_AUTH_SECRET=randomstring123 # Chainlit cần cái này để mã hóa session
    depends_on:
      - backend

5. Trải nghiệm​


Chạy lại Docker:
Mã:
docker-compose up --build

Bây giờ, khi truy cập http://localhost:8501:
  1. Bạn sẽ bị chặn lại ở màn hình Login.
  2. Nhập User: admin / Pass: jarvis123.
  3. Vào trong chat bình thường.
  4. Thử dùng Postman gọi trực tiếp vào http://localhost:8000/chat mà không có Header -> Bị chặn ngay lập tức. 🛡️

Tổng kết​


Vậy là Jarvis đã được trang bị "áo giáp". Giờ đây bạn có thể yên tâm mở port trên Router để truy cập Jarvis từ xa, hoặc deploy lên VPS mà không sợ bị người lạ phá đám.
 
Back
Top