1. Cookie Authentication (쿠키 인증 방식)
가장 원초적인 인증 방식으로, 서버가 사용자 식별 정보(데이터)를 브라우저의 쿠키에 직접 저장하는 방식이다.
💻 코드 핵심 구현
로그인 (/login): 인증 성공 시 response.set_cookie를 통해 평문 username을 쿠키에 설정한다.
response.set_cookie(key="user_session", value=username, httponly=True)
인증 확인 (/me): FastAPI의 Cookie(None) 매개변수를 통해 브라우저가 자동으로 보낸 쿠키 값을 읽어 DB에서 사용자를 조회한다.
async def get_me(user_session: str | None = Cookie(None), db=Depends(get_db)):
로그아웃 (/logout): response.delete_cookie로 브라우저의 쿠키를 강제 삭제한다.
💡 코드 분석 피드백
보안 위험성: 코드에서 보듯 value=username 형태로 사용자의 식별 정보가 브라우저에 평문으로 노출된다.
사용자가 쿠키 값을 조작(user_session=admin)하여 다른 사람으로 위장할 수 있는 치명적인 약점이 존재한다.
2. Session Authentication (세션 인증 방식)
쿠키 인증의 보안 문제를 해결하기 위해, 사용자의 실제 정보는 서버(DB)에 안전하게 저장하고 브라우저에는 임의의 랜덤 문자열(Session ID)만 전달하는 방식이다.
💻 코드 핵심 구현
세션 테이블 구성: sessions 테이블을 만들어 ID와 유저명을 매핑한다.
CREATE TABLE IF NOT EXISTS sessions (session_id TEXT PRIMARY KEY, username TEXT NOT NULL)
로그인 (/login): uuid.uuid4()로 예측 불가능한 고유 ID를 생성하여 DB에 저장하고, 브라우저에는 유저명 대신 이 session_id만 쿠키로 만든다.
session_id = str(uuid.uuid4())
await db.execute("INSERT INTO sessions (session_id, username) VALUES (?, ?)", (session_id, username))
response.set_cookie(key="session_id", value=session_id, httponly=True)
• 인증 확인 (/me): 쿠키로 받아온 session_id가 세션 테이블에 존재하는지 확인하고 유저명을 꺼내온다.
• 로그아웃 (/logout): 브라우저의 쿠키를 지울 뿐만 아니라, 서버 DB에서도 세션 데이터를 삭제(DELETE FROM sessions...)하여 토큰을 무효화한다.
💡 코드 분석 피드백
보안성은 크게 향상되었지만, 매 요청마다 sessions 테이블을 조회해야 하므로
DB 인프라 비용이 증가하고 다중 서버 환경에서 세션 동기화 문제가 발생할 수 있다.
3. JWT Authentication (JSON Web Token 인증 방식)
서버의 세션 저장소 부담을 없애기 위해 등장한 Stateless(상태를 저장하지 않는) 인증 방식이다.
정보를 토큰 자체에 암호화(서명)하여 들고 다닌다.
💻 코드 핵심 구현
토큰 생성 (create_access_token): RFC 7519 규격에 맞춰 유저 정보(sub)와 만료 시간(exp)을 담고, 서버만 아는 SECRET_KEY와 HS256 알고리즘으로 서명(Encode)한다.
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
...
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
로그인 (/login): 성공 시 쿠키가 아닌 JSON 바디로 토큰을 반환한다.
return {"access_token": access_token, "token_type": "bearer"}
인증 확인 (/me): FastAPI의 의존성 주입(Depends)과 OAuth2PasswordBearer를 사용하여,
요청 헤더(Authorization: Bearer <TOKEN>)에서 토큰을 추출하고 검증(Decode)한다.
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
4. JWT + Frontend(jQuery) 연동 및 FastAPI Depends 활용
마지막 코드(05_jwt2.py)는 전형적인 현대적인 웹 애플리케이션(SPA 스타일)의 API 서버 패턴을 보여준다.
💻 코드 핵심 구현
Pydantic 모델 도입: 폼 데이터(Form(...)) 대신 JSON 바디 기반의 통신을 위해 UserCreate, UserLogin 형식을 정의다.
class UserLogin(BaseModel):
username: str
password: str
정적 파일 서빙: 프론트엔드 파일(HTML, JS, CSS)을 백엔드에서 서빙할 수 있도록 마운트했다.
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
의존성 주입 분리 (get_current_user): 인증 로직을 독립된 함수로 분리하여 코드의 재사용성을 극대화했다.
# 독립된 인증 함수
async def get_current_user(token: str = Depends(oauth2_scheme), db=Depends(get_db)):
...
return user["username"]
# 필요한 엔드포인트에서 주입하여 사용
@app.get("/me")
async def get_me(current_user: str = Depends(get_current_user)):
return {"username": current_user}
💡 프론트엔드(jQuery) 동작 원리 매핑
로그인 시: 위 API가 주는 access_token을 받아 브라우저의 localStorage.setItem("access_token", ...)으로 저장한다.
인증이 필요한 API 호출 시: jQuery $.ajax 요청 시 헤더에 Authorization: 'Bearer ' + token을 담아 보낸다.
백엔드의 oauth2_scheme가 이 헤더를 감지하여 유효성을 검사하게 된다.
메무아
이번 실습을 통해 Cookie, Session, JWT 인증 방식이 단순히 다른 기술이 아니라 각각 이전 방식의 한계를 보완하며 발전해왔다는 점을 이해할 수 있었다. 특히 JWT를 직접 구현하면서 서버가 상태를 저장하지 않고도 인증을 처리하는 방식을 경험할 수 있었고, FastAPI의 Depends()를 활용한 인증 구조도 학습할 수 있었다.
다만 현재 구현한 JWT는 Access Token만 사용하는 구조이기 때문에 토큰 만료 시 재로그인이 필요하다는 한계가 있다. 다음에는 Refresh Token을 추가하여 Access Token + Refresh Token 구조를 구현해보며 보다 실무적인 인증 시스템을 경험해보고 싶다.
—————————————————————————————————————————————————————
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리]
한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
