6.3장: 기본 인증
학습 시간: 1시간
1. API 인증: 임무 통제 센터 입장 허가증
인증 — 사용자 신원을 확인하는 과정입니다. 세션 및 쿠키를 사용하는 웹사이트와 달리, 스테이트리스(상태 비저장) API는 일반적으로 토큰을 사용합니다.
과정은 다음과 같습니다:
- 사용자는 자신의 로그인 정보와 비밀번호를 특정 엔드포인트(예:
/login
)로 전송합니다. - 서버는 이를 확인합니다. 모든 것이 올바르면 서버는 고유하게 암호화된 토큰(긴 문자열)을 생성하여 다시 보냅니다.
- 보호된 리소스(예:
POST /planets
)에 대한 후속 요청 시 사용자는Authorization
헤더에 이 토큰을 첨부해야 합니다. - 서버는 토큰의 유효성을 확인하고, 토큰이 올바르면 요청을 실행합니다.
💡 우주 비유:
- 로그인/비밀번호 = 출입증 발급을 위한 생체 스캔.
- 토큰 = 임무 통제 센터 입구에서 받는 전자 출입증(ID 카드).
Authorization: Bearer <토큰>
헤더 = 모든 보호된 문에서 리더기에 출입증을 갖다 댑니다.- 보호된 엔드포인트 (POST, PUT, DELETE) = 서버실 또는 발사 제어 콘솔로 향하는 문.
2. Laravel에서의 인증: Sanctum
Laravel은 API 인증을 위한 우아한 솔루션인 Laravel Sanctum을 제공합니다. 이는 SPA(단일 페이지 애플리케이션), 모바일 애플리케이션 및 간단한 토큰 기반 API에 이상적입니다.
단계 1: Sanctum 설치 및 구성
Sanctum은 표준 Laravel 애플리케이션에 이미 설치되어 있지만, 구성을 확인해 보겠습니다.
- 구성 발행 (아직 하지 않았다면):
- 마이그레이션 실행 (
personal_access_tokens
테이블 생성): - User 모델에 트레이트 추가:
app/Models/User.php
를 열고HasApiTokens
트레이트를 사용하는지 확인하십시오.
단계 2: 토큰 발급을 위한 엔드포인트 생성 사용자가 로그인/비밀번호를 보낼 라우트가 필요합니다.
routes/api.php
에 추가:
// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['로그인 정보가 잘못되었습니다.'],
]);
}
// 토큰 반환
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken
]);
});
테스트를 위해 시더(seeder) 또는 Tinker를 통해 사용자를 생성할 수 있습니다.
단계 3: 라우트 보호
이제 CRUD 작업을 보호하겠습니다. routes/api.php
를 변경합니다:
// routes/api.php
use App\Http\Controllers\PlanetController;
// 행성 보기를 위한 공개 라우트
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
// 보호된 라우트 그룹
Route::middleware('auth:sanctum')->group(function () {
Route::post('/planets', [PlanetController::class, 'store']);
Route::put('/planets/{planet}', [PlanetController::class, 'update']);
Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);
// 로그아웃(토큰 삭제)을 위한 라우트
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => '성공적으로 로그아웃되었습니다'], 200);
});
});
미들웨어 auth:sanctum
은 Authorization
헤더에 유효한 토큰이 있는지 확인할 것입니다.
3. FastAPI에서의 인증: OAuth2 및 JWT
FastAPI에는 내장된 인증 시스템이 없지만, 이를 구현하기 위한 강력한 도구가 있습니다. 사실상의 표준은 JWT 토큰을 사용한 OAuth2입니다.
단계 1: 종속성 설치
python-jose
: JWT 토큰 생성 및 확인용.passlib
: 비밀번호 해싱 및 확인용.python-multipart
: 폼 데이터(username
및password
) 처리용.
단계 2: 보안 모듈 생성 (security.py
)
모든 인증 로직을 별도의 파일로 분리하는 것이 좋은 방법입니다.
security.py
파일 생성:
# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone
# --- 설정 ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ 본인의 키로 교체하세요!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- 유틸리티 ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")
# --- 함수 ---
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# --- 토큰 확인을 위한 의존성 함수 ---
def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="로그인 정보를 확인할 수 없습니다",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# 여기서 DB에서 사용자를 반환할 수 있지만, 지금은 단순히 이름을 반환합니다
return {"username": username}
단계 3: main.py
에 통합
이제 이것을 우리 애플리케이션에 연결하겠습니다.
-
/login
엔드포인트 생성:# main.py from fastapi.security import OAuth2PasswordRequestForm from fastapi import Depends, APIRouter from . import security # 우리 모듈을 임포트합니다 # ... 당신의 FastAPI 코드 ... router = APIRouter(prefix="/api/v1") @router.post("/login") def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # 여기에 DB 사용자 검증 로직이 있어야 합니다 # 예를 들어, 하나의 테스트 사용자가 있습니다 is_user_valid = (form_data.username == "testuser" and security.verify_password("testpass", security.get_password_hash("testpass"))) if not is_user_valid: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="잘못된 사용자 이름 또는 비밀번호", ) access_token = security.create_access_token(data={"sub": form_data.username}) return {"access_token": access_token, "token_type": "bearer"} # ... app.include_router(router)
-
엔드포인트 보호:
우리 의존성
get_current_user
를 사용합니다.# main.py 또는 당신의 행성 라우터에서 @router.post("/planets", status_code=status.HTTP_201_CREATED) def create_planet( planet: PlanetCreate, current_user: dict = Depends(security.get_current_user) # <-- 보호! ): // 행성 생성 로직... print(f"사용자 {current_user['username']}이(가) 행성을 생성합니다.") // ... return new_planet // PUT 및 DELETE도 보호합니다
4. 프런트엔드에서 토큰 사용
이제 우리 프런트엔드는 먼저 토큰을 얻고, 그것을 저장하고(예: localStorage
에), 각 보호된 요청에 첨부해야 합니다.
JavaScript (fetch
) 예시:
// 1. 로그인
async function login(email, password) {
const response = await fetch('http://localhost:8001/api/login', { // Laravel API 주소
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
});
const data = await response.json();
if (data.token) {
localStorage.setItem('api_token', data.token); // 토큰 저장
}
}
// 2. 보호된 요청 수행
async function createPlanet(planetData) {
const token = localStorage.getItem('api_token');
const response = await fetch('http://localhost:8001/api/planets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // <--- 토큰 첨부!
},
body: JSON.stringify(planetData)
});
// ...
}
복습 퀴즈
🚀 장 요약:
API에 "액세스 제어 시스템"을 구축했습니다. 이제 누구나 당신의 "은하계 데이터베이스"를 수정할 수 없습니다.
- ✅ 토큰 기반 인증의 원리를 이해했습니다.
- 🔐 Laravel Sanctum에서 토큰 발급 및 라우트 보호를 구현했습니다.
- ⚙️ FastAPI에서 OAuth2 및 JWT 기반 인증을 설정했습니다.
- 🛰️ 프론트엔드가 토큰을 저장하고 사용하는 방법을 배웠습니다.
당신의 API는 기능적일 뿐만 아니라 안전해졌습니다. 그러나 다른 개발자들이 이를 사용하려면 "사용 설명서"가 필요합니다.
📌 확인:
- Postman 또는 Insomnia를 사용하여 토큰 없이
/api/planets
(Laravel) 또는/api/v1/planets
(FastAPI)에POST
요청을 시도해보십시오.401 Unauthorized
오류를 받아야 합니다./login
에 요청을 보내 토큰을 받고, 이를Authorization
헤더에 추가한 다음POST
요청을 다시 시도해보십시오. 요청이 성공적으로 완료되어야 합니다.