Ачааллж байна...
Одоог хүртэл таны API-д хэн ч бүх зүйл хийж чадна. Хэн ч тэмдэглэл унших, үүсгэх, засах, устгах боломжтой. Компьютер дээрээ дан ганцаараа ажиллаж байхад энэ нь асуудал биш. Гэхдээ энэ API интернэтэд гарвал хэн нэгэн бүх тэмдэглэлийг тань устгаж чадна.
Энэ хичээлд бид анхны хамгаалалтаа нэмнэ: API key. Бичих үйлдлүүд (үүсгэх, засах, устгах) зөв түлхүүр шаардана; унших үйлдэл нээлттэй үлдэнэ.
Мөн бид Depends-ийн хоёр дахь, илүү хүчирхэг чадварыг харна: dependency нь зөвхөн өгөгдөл бэлддэггүй — хүсэлтийг зогсоож ч чадна.
Эхлээд шударга хүрээ тогтооё, учир нь энэ нь чухал.
Бид заах зүйл: API key. Ганц нууц түлхүүр, серверт мэдэгдсэн. Хэн тэр түлхүүрийг мэдэж байвал бичих үйлдэл хийж чадна. Энэ бол бодит, өдөр тутам ашиглагддаг арга — олон компанийн API яг ингэж хамгаалагддаг (жишээ нь цаг агаарын API-ууд, төлбөрийн үйлчилгээнүүд).
Бид заахгүй зүйл: хэрэглэгчийн нэвтрэлт. Өөрөөр хэлбэл "хэрэглэгч бүр өөрийн бүртгэлтэй, өөрийн нууц үгтэй, өөрийн өгөгдөлтэй" гэсэн систем. Тэр нь OAuth2 болон JWT token гэсэн технологиуд шаарддаг бөгөөд өөрөө том сэдэв юм — нууц үг хэшлэх, token үүсгэх, хугацаа дуусах, шинэчлэх, эрх ялгах.
Хоёрын ялгаа нь энэ: API key нь "чи зөвшөөрөгдсөн программ мөн үү" гэдгийг шалгадаг; хэрэглэгчийн нэвтрэлт нь "чи хэн бэ" гэдгийг шалгадаг. API key-тэй систем бүх хэрэглэгчийг ижилхэн үздэг. Нэвтрэлттэй систем хэрэглэгч бүрийг ялгадаг.
Бүлэг 15-т бид OAuth2/JWT-г цаашдын зам болгон нэрлэнэ. Одоохондоо API key нь таны API-г үндсэн хамгаалалттай болгоно.
Зоогийн газарт хоёр төрлийн орон зай байдаг.
Танхим — зочид чөлөөтэй ордог. Хэн ч орж, суулж, цэс харж болно. Хаалга онгорхой.
Гал тогоо, агуулах — зөвхөн ажилтан ордог. Хаалган дээр ажилтны үнэмлэх шалгадаг. Үнэмлэхгүй хүн орох гэвэл зогсоогдоно.
Яагаад ялгаатай вэ? Учир нь танхимд байгаа хүн юу ч эвдэхгүй — зөвхөн харна. Гэхдээ агуулахад орсон хүн бараа авах, өөрчлөх, устгах боломжтой.
Таны API ч яг ийм. Унших endpoint (GET) нь танхим — хэн ч харж болно. Бичих endpoint (POST, PUT, DELETE) нь агуулах — зөвхөн үнэмлэхтэй хүн.
API key бол тэр ажилтны үнэмлэх юм.
Түлхүүрийг хаана илгээх вэ? Гурван сонголт бий, зөвхөн нэг нь зөв.
Хаягт (query parameter) — ?api_key=нууц123. Буруу. Хаяг нь browser-ийн түүхэнд үлддэг, log-д бичигддэг, хуваалцахад амархан. Нууц зүйлийг хаягт хэзээ ч бүү тавь.
Body-д — {"api_key": "нууц123", "text": "..."}. Буруу. Body бол өгөгдөл; түлхүүр бол өгөгдөл биш, зөвшөөрөл. Мөн GET болон DELETE-д body байдаггүй.
Header-д — зөв. Header бол HTTP хүсэлтийн "дугтуйн дээрх бичиг" юм: хаана явж байгаа, ямар форматтай, хэн илгээж байгаа гэх мэт мета мэдээлэл. Агуулга биш, агуулгын тухай мэдээлэл.
Та header-ийг өмнө нь харсан. Бүлэг 5-ын Хичээл 3-т /docs-ийн curl тушаалд ийм мөр байсан:
-H 'Content-Type: application/json'Тэр -H нь header. Content-Type: application/json гэдэг нь "би JSON илгээж байна" гэсэн мета мэдээлэл.
Header нь нэр: утга хэлбэртэй. Бид X-API-Key гэсэн нэртэй header ашиглана. X- угтвар нь түүхэн шалтгаантай — "стандарт бус, тухайн API-ийн өөрийн header" гэсэн утгатай бөгөөд одоо ч түгээмэл ашиглагддаг.
Одоо код руу орно. main.py файлаа дараах кодоор солино:
python
# main.py
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI(title="Тэмдэглэлийн API")
API_KEY = "нууц-түлхүүр-12345"
notes = [
{"id": 1, "text": "Сүү авах", "done": False},
{"id": 2, "text": "Номоо буцаах", "done": True},
]
next_id = 3
def verify_api_key(x_api_key: str = Header()):
if x_api_key != API_KEY:
raise HTTPException(status_code=403, detail="Түлхүүр буруу байна")
return x_api_key
@app.get("/notes")
def get_notes():
return notes
@app.post("/notes", status_code=201)
def create_note(text: str, key: str = Depends(verify_api_key)):
global next_id
new_note = {"id": next_id, "text": text, "done": False}
notes.append(new_note)
next_id += 1
return new_noteХадгална.
Кодын задаргаа
from fastapi import ... Header — Header-ыг import хийж байна.
API_KEY = "нууц-түлхүүр-12345" — түлхүүрийг кодонд шууд бичсэн. Энэ бол түр зуурын, буруу шийдэл — бид Бүлэг 9-т үүнийг засна. Одоохондоо механизмд анхаарлаа төвлөрүүлье.
python
def verify_api_key(x_api_key: str = Header()):Энэ бол dependency функц. Гэхдээ нэг шинэ зүйл: параметрийн default нь Header().
Бүлэг 8-ын Хичээл 1-2-т dependency-ийн параметрүүд query parameter болж байсан. Одоо Header() гэж бичсэнээр FastAPI-д "энэ утгыг хаягнаас биш, header-ээс ав" гэж хэлж байна.
Нэрийг сайтар анзаараарай: параметрийн нэр нь x_api_key (доогуур зураастай, Python хэв маяг), гэхдээ header-ийн жинхэнэ нэр нь X-API-Key (зураастай, HTTP хэв маяг). FastAPI хоёрыг автоматаар хөрвүүлдэг: доогуур зураасыг зураас болгож, том жижиг үсгийг тоохгүй. Тиймээс та Python-ы хэв маягаар бичихэд FastAPI HTTP-ийн хэв маягтай тааруулна.
python
if x_api_key != API_KEY:
raise HTTPException(status_code=403, detail="Түлхүүр буруу байна")
return x_api_keyЭнэ бол хичээлийн гол шинэ санаа. Dependency нь HTTPException шидэж байна.
Юу болох вэ? Хэрэв түлхүүр буруу бол dependency зогсоно, HTTPException шидэгдэнэ, FastAPI түүнийг барьж 403 хариу болгоно, endpoint огт дуудагдахгүй.
Өмнөх хичээлүүдэд dependency нь зөвхөн өгөгдөл бэлддэг байсан. Одоо тэр хаалганы харуул болж байна. Зөвшөөрсөн бол хүсэлт цааш явна; зөвшөөрөөгүй бол тэндээ зогсоно.
return x_api_key — зөв бол түлхүүрийг буцаана. Энэ утга endpoint-ийн key параметрт орно. Практикт бид түүнийг ашиглахгүй, гэхдээ dependency нь ямар нэг зүйл буцаах ёстой.
python
@app.post("/notes", status_code=201)
def create_note(text: str, key: str = Depends(verify_api_key)):POST endpoint нь Depends(verify_api_key)-тэй. Энэ нь "энэ endpoint дуудагдахаас өмнө түлхүүр шалга" гэсэн үг.
get_notes (GET) нь dependency-гүй — унших нээлттэй.
Унших — нээлттэй
http://127.0.0.1:8000/notesХариу (статус 200):
json
[{"id":1,"text":"Сүү авах","done":false},{"id":2,"text":"Номоо буцаах","done":true}]Түлхүүргүй ажиллалаа. Унших нээлттэй.
Бичих — түлхүүргүй
/docs -> POST /notes -> Try it out. text талбарт ямар нэг текст бичээд, x-api-key талбарыг хоосон орхиод Execute.
Server response (статус 422):
json
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-api-key"],
"msg": "Field required",
"input": null
}
]
}Танил бүтэц. Гэхдээ loc талбарыг хараарай: ["header", "x-api-key"].
Бүлэг 2-6-т бид "path", "query", "body" гэсэн утгуудыг харсан. Одоо дөрөв дэх байршил: "header". FastAPI өгөгдөл хаанаас ирэх ёстойг яг мэдэж, дутуу байгааг тодорхой хэлж байна.
Бичих — буруу түлхүүр
x-api-key талбарт буруу-түлхүүр гэж бичээд Execute.
Server response (статус 403):
json
{"detail":"Түлхүүр буруу байна"}403 — Forbidden. Бүлэг 3-т сурсанчлан "танигдсан боловч зөвшөөрөгдөөгүй".
Endpoint огт дуудагдаагүй. Тэмдэглэл үүсээгүй. Dependency хаалган дээр зогсоосон.
Бичих — зөв түлхүүр
x-api-key талбарт нууц-түлхүүр-12345 гэж бичээд, text талбарт "Шинэ тэмдэглэл" гээд Execute.
Server response (статус 201):
json
{"id":3,"text":"Шинэ тэмдэглэл","done":false}Ажиллалаа. Түлхүүр зөв тул dependency зөвшөөрч, endpoint дуудагдаж, тэмдэглэл үүссэн.
GET-ээр батлана: http://127.0.0.1:8000/notes — гурван тэмдэглэл.
Бүлэг 3-т бид эдгээрийг товч дурдсан. Одоо тодруулъя, учир нь энэ нь эргэлзээ үүсгэдэг.
401 (Unauthorized) — "Та хэн бэ гэдэг нь танигдаагүй." Нэвтрэх мэдээлэл огт өгөгдөөгүй, эсвэл буруу форматтай.
403 (Forbidden) — "Та хэн бэ гэдэг нь мэдэгдсэн, гэхдээ энэ үйлдэл танд зөвшөөрөгдөөгүй."
Зочид буудлын жишээ: 401 бол түлхүүрээ мартсан зочин (та хэн бэ?); 403 бол өөрийн түлхүүртэй боловч өөр хүний өрөө рүү орох гэж байгаа зочин (та хэн бэ мэдэж байна, гэхдээ болохгүй).
API key-ийн хувьд аль нь зөв бэ? Хоёулаа хэрэглэгддэг. Практикт олон API буруу түлхүүрт 403 буцаадаг, зарим нь 401. Бид 403-ыг ашиглаж байна.
Нарийн хуваавал: түлхүүр огт байхгүй бол 401, түлхүүр байгаа боловч буруу бол 403 гэж болно. Гэхдээ бидний тохиолдолд түлхүүр байхгүй бол FastAPI 422 буцаадаг (Header() нь заавал тул), тиймээс зөвхөн 403 хэрэгтэй болж байна.
Гол зарчим: тодорхой, тогтвортой байх. Аль тоог сонгосон ч түүнийгээ бүх endpoint дээр ижилхэн ашигла.
Одоо бүрэн жишээ рүү орно. Тэмдэглэлийн API-ийн бүх бичих үйлдлийг хамгаалъя:
python
# main.py
from fastapi import FastAPI, Depends, HTTPException, Header
from pydantic import BaseModel
app = FastAPI(title="Тэмдэглэлийн API")
API_KEY = "нууц-түлхүүр-12345"
notes: list[dict] = []
next_id = 1
class NoteCreate(BaseModel):
text: str
done: bool = False
class NotePublic(BaseModel):
id: int
text: str
done: bool
def verify_api_key(x_api_key: str = Header()):
if x_api_key != API_KEY:
raise HTTPException(status_code=403, detail="Түлхүүр буруу байна")
return x_api_key
@app.get("/notes", response_model=list[NotePublic])
def get_notes():
return notes
@app.get("/notes/{note_id}", response_model=NotePublic)
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
@app.post(
"/notes",
status_code=201,
response_model=NotePublic,
dependencies=[Depends(verify_api_key)],
)
def create_note(note: NoteCreate):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_note
@app.put(
"/notes/{note_id}",
response_model=NotePublic,
dependencies=[Depends(verify_api_key)],
)
def update_note(note_id: int, note: NoteCreate):
for existing in notes:
if existing["id"] == note_id:
existing["text"] = note.text
existing["done"] = note.done
return existing
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")
@app.delete(
"/notes/{note_id}",
status_code=204,
dependencies=[Depends(verify_api_key)],
)
def delete_note(note_id: int):
for i, note in enumerate(notes):
if note["id"] == note_id:
notes.pop(i)
return None
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Шинэ бичлэг: dependencies=[...]
Энд шинэ зүйл байна. Өмнө нь бид ингэж бичсэн:
python
def create_note(note: NoteCreate, key: str = Depends(verify_api_key)):Одоо ингэж:
python
@app.post("/notes", dependencies=[Depends(verify_api_key)])
def create_note(note: NoteCreate):Ялгаа нь: dependency нь decorator дээр, функцийн параметрт биш.
Яагаад? Учир нь бид verify_api_key-ийн буцаах утгыг ашигладаггүй. Түүний ажил бол зогсоох эсвэл зөвшөөрөх — үр дүн нь бидэнд хэрэггүй. Тиймээс түүнийг функцийн параметрт оруулах нь илүүц; тэр зөвхөн кодыг бөглөрүүлнэ.
dependencies=[...] гэсэн бичлэг нь: "энэ dependency-г ажиллуул, гэхдээ үр дүнг нь надад өгөх шаардлагагүй". Хамгаалалтын dependency-д яг тохирно.
dependencies нь жагсаалт гэдгийг анзаараарай — олон dependency зэрэг зарлаж болно.
Дүрэм: үр дүн хэрэгтэй бол параметрт; зөвхөн шалгалт бол dependencies=[...]-д.
Туршина
/docs дээр endpoint-үүдийг харна. Хоёр GET нь x-api-key талбаргүй. POST, PUT, DELETE гурав нь x-api-key талбартай (Parameters хэсэгт, header гэж тэмдэглэгдсэн).
GET — түлхүүргүй ажиллана.
POST түлхүүргүй — 422 (missing, loc: ["header", "x-api-key"]).
POST буруу түлхүүртэй — 403.
POST зөв түлхүүртэй — 201, тэмдэглэл үүснэ.
DELETE зөв түлхүүртэй — 204, устана.
Таны API одоо анх удаа жинхэнэ хамгаалалттай боллоо. Унших нээлттэй, бичих хаалттай.
Хэрэв бүх endpoint-ыг (эсвэл router-ийн бүх endpoint-ыг) хамгаалахыг хүсвэл router түвшинд зарлаж болно:
python
router = APIRouter(
prefix="/admin",
tags=["Админ"],
dependencies=[Depends(verify_api_key)],
)Одоо энэ router-ийн бүх endpoint автоматаар түлхүүр шаардана. Тус бүрт бичих шаардлагагүй.
Энэ нь ялангуяа админы хэсэгт ашигтай: бүх админ endpoint нэг router-т, router бүхэлдээ хамгаалагдсан. Шинэ endpoint нэмэхэд автоматаар хамгаалагдана — хамгаалалт нэмэхээ мартах эрсдэлгүй.
Capstone-д бид энэ загварыг ашиглана.
Шударга байя. Энэ шийдэл ажиллаж байгаа боловч нэг ноцтой алдаатай:
python
API_KEY = "нууц-түлхүүр-12345"Түлхүүр кодонд шууд бичигдсэн. Энэ нь хэд хэдэн асуудал үүсгэнэ.
Хэрэв та кодоо GitHub-д байршуулбал түлхүүр тань олон нийтэд ил гарна. Хэн ч түүнийг уншиж, таны API-д бичих эрх авна.
Хэрэв түлхүүрээ солихыг хүсвэл кодоо засаж, дахин байршуулах ёстой.
Хэрэв туршилтын сервер болон бодит сервер өөр түлхүүртэй байх ёстой бол — кодонд аль нь байх вэ?
Дараагийн бүлэг (Бүлэг 9) энэ асуудлыг шийднэ. Тэнд бид environment variable сурна: нууц зүйлийг кодоос гаргаж, тусдаа файлд (эсвэл серверийн тохиргоонд) хадгална. Код нь нээлттэй байж болно; нууц нь тусдаа үлдэнэ.
Одоохондоо энэ дутагдлыг мэдэж байгаад орхиё. Механизм зөв; хадгалалт нь буруу.
Header() хаалтгүй бичих
python
def verify_api_key(x_api_key: str = Header): # хаалт байхгүйHeader бол class; Header() бол түүний объект. Хаалтгүй бол FastAPI түүнийг default утга гэж үзэж, хачирхалтай зан төлөв гарна. Зөв: Header().
Header-ийн нэрийг буруу бичих
python
def verify_api_key(api_key: str = Header()): # x_ угтваргүйЭнэ нь api-key гэсэн header хайна, x-api-key биш. Хэрэглэгч X-API-Key илгээвэл олдохгүй, 422 гарна.
Параметрийн нэр нь header-ийн нэртэй тааруулагдана: x_api_key -> x-api-key. Тиймээс нэрийг зөв бичих нь чухал.
Түлхүүрийг query parameter болгох
python
@app.post("/notes")
def create_note(note: NoteCreate, api_key: str): # query parameter!Энэ ажиллана, гэхдээ буруу. Түлхүүр хаягт гарч, browser-ийн түүх, серверийн log, хуваалцсан холбоос бүрд үлдэнэ. Нууц зүйл header-т явна.
Бүх endpoint-ыг хамгаалах
python
@app.get("/notes", dependencies=[Depends(verify_api_key)]) # унших ч хаалттайЭнэ нь ажиллана, гэхдээ таны хүсэл мөн үү? Хэрэв API нь олон нийтэд нээлттэй мэдээлэл өгөх ёстой бол унших endpoint-ыг хамгаалах нь түүнийг ашиггүй болгоно.
Шийдвэрийг ухамсартай гарга: юуг нээлттэй, юуг хаалттай байлгах вэ? Ерөнхий зарчим: унших нээлттэй, бичих хаалттай. Гэхдээ хувийн өгөгдөлтэй API-д унших ч хаалттай байх ёстой.
return мартах
python
def verify_api_key(x_api_key: str = Header()):
if x_api_key != API_KEY:
raise HTTPException(status_code=403, detail="Түлхүүр буруу байна")
# return байхгүйdependencies=[...] хэлбэрээр ашиглавал энэ нь асуудалгүй — буцаах утга хэрэггүй. Гэхдээ параметрт ашиглавал key нь None болно. Зуршил болгон return бичих нь зүйтэй.
Хүсвэл түлхүүрийг өөрчилж (жишээ нь API_KEY = "шинэ-түлхүүр"), хадгалаад, хуучин түлхүүрээр POST илгээж үзээрэй. 403 гарна. Дараа нь шинэ түлхүүрээр — ажиллана. Хамгаалалт бодитоор ажиллаж байгааг гар дээрээ мэдрэх нь ойлголтыг бататгана.
Сонирхвол verify_api_key дотор print(f"Түлхүүр шалгаж байна: {x_api_key}") нэмээд, зөв ба буруу түлхүүрээр туршаад терминалыг ажиглаарай. Dependency хүсэлт бүрд ажиллаж байгааг, зөвхөн зөв тохиолдолд endpoint дуудагдаж байгааг харна.
API key нь "чи зөвшөөрөгдсөн программ мөн үү" гэдгийг шалгана; хэрэглэгчийн нэвтрэлт (OAuth2/JWT) нь "чи хэн бэ" гэдгийг шалгана — сүүлийнх нь энэ сургалтын хүрээнээс гадуур.
Зүйрлэл: ажилтны үнэмлэх — танхим нээлттэй (унших), агуулах хаалттай (бичих).
Түлхүүр header-т явна (X-API-Key) — хаягт эсвэл body-д хэзээ ч биш.
x_api_key: str = Header() — FastAPI доогуур зураасыг зураас болгож хөрвүүлнэ.
Dependency нь HTTPException шидэж, хүсэлтийг зогсоож чадна — endpoint огт дуудагдахгүй.
Алдааны loc дөрөв дэх байршил: ["header", "x-api-key"].
403 (Forbidden) — танигдсан боловч зөвшөөрөгдөөгүй. 401 — танигдаагүй. Тогтвортой сонго.
dependencies=[Depends(...)] — үр дүн хэрэггүй үед (зөвхөн шалгалт). Параметрт — үр дүн хэрэгтэй үед.
Router түвшинд dependencies=[...] зарлавал бүх endpoint автоматаар хамгаалагдана.
Одоогийн сул тал: түлхүүр кодонд бичигдсэн — Бүлэг 9 үүнийг засна.
Бүлэг 8 дууслаа. Та одоо Depends-ийн хоёр үүргийг мэднэ: өгөгдөл бэлдэх (Хичээл 1-2) болон хүсэлтийг шалгаж зогсоох (Хичээл 3). Энэ бол FastAPI-ийн хамгийн хүчирхэг механизмуудын нэг бөгөөд Бүлэг 12-т өгөгдлийн сангийн session түүн дээр бүрэн тулгуурлана.
Дараагийн бүлэгт бид энэ бүлгийн үлдээсэн асуудлыг шийднэ: нууцаа кодоос салгах. Environment variable болон .env файл ашиглан API key-ээ кодоос гаргана. Дараа нь алдааны хариугаа өөриймсүүлж, эцэст нь Тэмдэглэлийн API v2 бүтээнэ — v1-ийг олон файлт бүтэц рүү шилжүүлж, API key-ээр хамгаалж, тохиргоог кодоос салгасан мэргэжлийн хувилбар.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.