TOP
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] FastAPI - 고급 인증: JWT

by 기록자_Recordian 2025. 5. 6.
728x90
반응형
이전 내용
 

[FastAPI] 인증과 세션

이전 내용 [FastAPI] SQLAlchemy와 CRUD (Depends, db.query)이전 내용 [파이썬] FastAPI - ORM 연동하기(SQLAlchemy)이전 내용 [파이썬] FastAPI - 웹소켓이전 내용 [파이썬] FastAPI - 스트리밍 응답이전 내용 [파이썬] Fast

puppy-foot-it.tistory.com


고급인증: JWT

 

◆ JWT(JSON Web Token)

JWT는 클라이언트와 서버 등의 두 대상 사이에 정보를 안전하게 전송하기 위한 토큰으로, 주로 웹, 모바일 애플리케이션에서 사용되며 사용자 인증이나 정보 교환에 많이 쓰인다.

서버는 사용자가 로그인을 완료하면 JWT를 생성하고 이를 클라이언트에 반환하고, 클라이언트는 이후의 요청에서 이 JWT를 사용하여 인증을 수행한다.

 

[장단점]

  • 장점: 빠른 인증 처리 가능 / 사용자 상태를 서버에 저장 불필요
  • 단점: 만료 시간이 정해져 있지 않을 경우 토큰이 계속 유효 / 페이로드 내용이 외부에 노출될 수 있음.

◆ JWT가 필요한 이유

  • 스테이트리스(stateless, 상태 없음) 인증: JWT는 서버에 세션 정보를 저장할 필요가 없으므로, 애플리케이션이 스테이트리스하게 동작할 수 있고, 클라이언트는 자신의 JWT를 서버에 제출하여 인증을 받을 수 있다. ▶ 각 토큰이 자체적으로 정보를 가지고 있기 때문에 서버가 사용자의 상태를 기억하지 않아도 되어 서버의 메모리를 절약할 수 있다.
  • 확장성: JWT는 다양한 서비스 간의 인증을 지원하기 때문에, 마이크로서비스 아키텍처에서 유용하다. ▶ 사용자 정보를 토큰 안에 포함시킬 수 있으므로, 서버는 별도의 저장소에 사용자 정보를 저장할 필요가 없다.
  • 안전성: JWT는 서명을 통해 변조를 방지할 수 있으며, 다양한 암호화 방식으로 데이터의 안전성을 더욱 강화할 수 있다.
  • 정보 교환: 여러 시스템 간의 안전한 정보 전송이 가능하여, 사용자 정보 및 권한 등의 데이터를 안전하게 전달할 수 있다.
  • 분산된 시스템 지원: 하나의 서비스가 여러 서버나 도메인으로 분산되어 있을 때 토큰 기반 시스템이 유용하다.

◆ JWT의 구성 요소
- 헤더(Header): JWT의 유형(typ)과 서명 알고리즘(alg)을 지정한다. 토큰의 타입과 사용된 알고리즘 정보가 들어있다.
예: {"alg": "HS256", "typ": "JWT"}
- 페이로드(Payload): 실제 전달하려는 사용자에 대한 정보(클레임)가 담겨 있다. 클레임은 기본 클레임과 사용자 정의 클레임으로 나눌 수 있다.
예: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}

- 서명(Signature): 헤더와 페이로드를 인코딩하여 비밀 키를 사용해 서명한 것으로, 이 서명은 데이터가 변조되지 않았음을 확인하는 데 필요하다. 
서명 생성 예: HMACSHA256base64UrlEncode(header + "." + base64UrlEncode(payload), your-256-bit-secret)

 

◆ JWT의 동작 방식

  • 인증 요청: 사용자가 로그인 정보를 서버에 제출.
  • 토큰 생성: 사용자가 로그인하면 서버는 사용자 정보와 비밀 키, 알고리즘을 이용해 JWT를 생성.
  • 토큰 전달: 생성된 토큰을 사용자에게 전달.
  • 토큰 저장: 클라이언트는 받은 토큰을 저장소에 보관
  • 토큰 사용: 클라이언트는 서버에 요청 시마다 토큰을 함께 보냄.
  • 토큰 검증: 서버는 토큰을 받으면 비밀 키를 이용해 검증하고, 검증에 성공하면 요청 처리.

좀 더 자세한 내용은

 

[Java] Spring Boot: 스프링 시큐리티, OAuth2, JWT

이전 내용 [Java] Spring Boot: 네이버 로그인 구현하기이전 내용 [Java] Spring Boot: 카카오 로그인 기능 추가하기이전 내용 [Java] Spring Boot: 구글 로그인 기능 추가하기이전 내용 전반적인 순서1. Gradle 또

puppy-foot-it.tistory.com


FastAPI에서의 JWT

 

FastAPI에서는 PyJWT 라이브러리를 이용해 JWT 토큰을 쉽게 생성하고 검증할 수 있다. 이를 위해 먼저 필요한 라이브러리를 설치해야 한다.

conda install pyjwt

 

[JWT를 생성하고 검증하는 FastAPI 애플리케이션]

from fastapi import FastAPI, HTTPException, Header
import jwt
import datetime

app = FastAPI()

SECRET_KEY = "my_secret_key"

# JWT 생성
def create_jwt_token(data: dict):
    expiration = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    data["exp"] = expiration # JWT의 "exp" 클레임에 만료 시간 설정
    return jwt.encode(data, SECRET_KEY, algorithm="HS256")

# JWT 검증
def verify_jwt_token(token: str):
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except:
        raise HTTPException(status_code=401, detail="Invalid token")
    
@app.get("/token")
def generate_token(username: str):
    return {"token": create_jwt_token({"username": username})}

@app.get("/protected")
def read_protected_route(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # "Bearer " 부분이 포함된 헤더에서 토큰 추출
    token = authorization.replace("Bearer ", "")
    verify_jwt_token(token)  # 토큰 검증

    return {"message": "This is a protected route"}

 

<코드 설명>

1. FastAPI 임포트 및 애플리케이션 생성

  • FastAPI 모듈: FastAPI는 Python으로 작성된 현대적인 웹 프레임워크로, 높은 성능과 간편한 사용성을 제공.
  • HTTPException과 Header: HTTPException은 HTTP 오류 상태를 반환하기 위한 예외 클래스이며, Header는 요청의 헤더 정보를 가져올 수 있게 해줌.
  • jwt: PyJWT 패키지에서 제공하는 jwt 모듈 임포트
  • datetime: 시간 관련 처리를 위한 datatime 모듈 임포트
  • app 객체: FastAPI 애플리케이션의 인스턴스를 생성

2. SECRET_KEY: 비밀 키 설정

  • 비밀 키: JWT를 생성하거나 검증할 때 사용하는 비밀 키로, 이 키는 외부에 노출되지 않도록 안전하게 보관해야 하며, 변경이 쉽지 않아야 한다.
  • 비밀키의 길이는 사용하는 알고리즘과 보안 요구 사항에 따라 다른데, H256을 사용할 경우 최소한 256비트(32바이트 또는 32자리 ASCII 문자) 이상을 권장. ▶ 길이가 길수록 무작위 공격에 대한 저항성이 높아지며, 복잡한 문자, 숫자, 특수 문자의 조합으로 이루어져야 한다.
  • 안전한 SECRET_KEY를 생성하기 위해 파이썬에서 secrets 라이브러리를 사용할 수 있다.
import secrets

new_key = secrets.token_hex(32) # 64 자리의 랜덤한 문자열 생성

 

3. create_jwt_token (JWT 생성) 함수

이 함수는 사용자의 데이터와 만료 시간을 포함하여 JWT를 생성한다.

  • 매개변수: 데이터는 일반적으로 사용자 관련 정보(예: 사용자 이름)를 포함하는 딕셔너리.
  • 만료 시간 설정: 현재 UTC 시간에 1시간을 추가하여 만료 시간 설정.
  • JWT 인코딩: jwt.encode() 메서드를 사용하여 주어진 데이터를 포함한 JWT를 생성하며, 이때 비밀 키와 해싱 알고리즘(HMAC SHA-256)을 사용.

4. verify_jwt_token (JWT 검증) 함수

주어진 JWT를 검증.

  • 매개변수: 검증할 JWT 토큰을 문자열로 받는다.
  • JWT 디코딩: jwt.decode() 메서드를 통해 유효성을 검사하고 유효한 경우, 디코딩된 페이로드를 반환.
  • 예외 처리: JWT가 유효하지 않거나 만료된 경우, HTTPException을 발생시켜 클라이언트에게 401 상태 코드를 전송.
  • jwt.decode() 함수와 예외 처리를 활용하면 토큰 검증을 더 안전하고 유연하게 할 수 있다.

5. /token 엔드포인트 (토큰 생성 엔드포인트)

사용자가 제공한 사용자 이름을 기반으로 JWT를 생성하는 HTTP GET 요청을 처리.

  • 매개변수: username 파라미터는 쿼리 문자열로 전달돼야 하며, 생성할 JWT에 포함.
  • 응답: 생성된 JWT를 JSON 형식으로 반환.

6. /protected 엔드포인트 (보호된 라우트)

보호된 API로, 인증된 사용자만 접근할 수 있다.

  • 매개변수: authorization 매개변수는 요청의 헤더에서 JWT를 받아온다. Header(None)을 통해 기본값을 설정.
  • 인증 여부 확인: 헤더에서 Authorization이 존재하지 않으면 401 상태 코드와 함께 오류 메시지를 반환.
  • 토큰 추출: Bearer 문자열을 제거한 후 실제 JWT 토큰을 추출.
  • 토큰 검증: verify_jwt_token 함수를 호출하여 토큰이 유효한지 확인.
  • 응답: JWT가 유효하다면 보호된 리소스에 대한 접근을 허용하고, 성공 메시지를 반환.

[JWT 테스트]

먼저, 포스트맨을 켜고 GET 메소드에 해당 url을 넣고 [SEND] 버튼을 누르면

http://127.0.0.1:8000/token?username=유저이름

 

아래와 같이 토큰이 발급된다.

 

 

[보호된 경로 테스트]

포스트맨에서 하단의 url로 주소를 변경한 뒤, Authorization 탭에서 Auth Type을 Bearer Token으로 바꾼다.

http://127.0.0.1:8000/protected

 

그리고 앞서 발급받은 토큰 번호를 복사하여 Token 란에 넣고 [Send] 버튼을 누르면 "This is a protected route" 메시지가 출력되는 것을 확인할 수 있다. (토큰이 유효할 경우)

 


expiration(만료) 설정

 

앞서, secrets 라이브러리를 사용하여 생성한 비밀키는 별도로 보관하고, 코드에서 해당 키를 불러오는 형태로 사용한다.

def create_jwt_token(data: dict):
    expiration = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    data["exp"] = expiration # JWT의 "exp" 클레임에 만료 시간 설정
    return jwt.encode(data, SECRET_KEY, algorithm="HS256")

 

여기서는 만료 시간을 UTC 현재 시각에 1시간을 더해서 설정했는데,  expiration 설정은 서비스의 특성과 보안 요구 사항에 따라 다르다. 파이썬의 datetime.timedelta를 사용하여 다양한 시간 설정을 통해 토큰의 만료 시간을 제어할 수 있다.

 

timedelta에는 days 외에도 다음 표와 같은 매개변수를 사용할 수 있다.

매개변수 설명
days
seconds 
microseconds  마이크로 초
milliseconds  밀리 초 (1밀리 초는 1000마이크로 초)
minutes 
hours  시간
weeks  주 (7일을 의미함)

 

◆ 분 단위 만료: 로그인 세션에 적합. (15분)

expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)

 

◆ 시간 단위 만료: 가장 많이 사용하는 설정

expiration = datetime.datetime.utcnow() + datetime.timedelta(hours=1)

 

◆ 하루 만료: 애플리케이션 내에서 꾸준한 사용이 이루어질 때 사용

expiration = datetime.datetime.utcnow() + datetime.timedelta(days=1)

 

◆ 주 단위 만료: 장기간 로그인을 유지해야 할 때 사용

expiration = datetime.datetime.utcnow() + datetime.timedelta(weeks=1)

 

◆ 연 단위 만료

expiration = datetime.datetime.utcnow() + datetime.timedelta(days=365)

 

◆ 월 단위 만료

datetime.timedelta 객체는 months 매개변수를 지원하지 않기 때문에 직접 사용할 수 없다. 대신, relativedelta를 사용하여 월 단위 계산을 쉽게 처리한다.

※ relativedelta는 datetime 라이브러리와 함께 사용하여 특정 시간 단위를 더하거나 뺄 수 있도록 도와준다.

expiration = datetime.datetime.utcnow() + relativedelta(months=1)

 

◆ 영구 토큰

만료 시간을 설정하지 않으면 영구 토큰도 가능하나, 토큰이 탈취되면 악의적인 사용자가 계속해서 해당 토큰을 사용할 수 있으므로 실무에서는 사용하지 않는다.

대신, 실무에서는

  • 두 단계 만료: 짧은 만료 시간을 가진 "액세스 토큰" + 긴 만료 시간을 가진 "리프레시 토큰" 함께 사용
  • 슬라이딩 세션: 사용자가 활동 할 때마다 토큰의 만료 시간 연장
  • MFA(Multi-Factor Authentication): 높은 보안이 필요한 경우 만료 시간을 더 짧게 설정하고 다단계 인증 도입 
  • 사용자 설정 만료: 사용자가 선택할 수 있는 토큰 만료 시간 제공

JWT 생성 옵션

 

[만료 설정 옵션]

jwt.encode(data, SECRET_KEY, algorithm="HS256")
  • data: 페이로드에 들어갈 데이터 부분 (ex. 사용자 권한, ID 등)
  • algorithm: WT(JSON Web Token) 생성 및 검증 시 사용되는 해싱 알고리즘을 의미하며, HS256 외에 HS384, HS512 등이 있음.

 알고리즘 선택 기준

  • 보안 수준: 일반적으로 비대칭 키 알고리즘이 대칭 키 알고리즘보다 보안성이 높다.
  • Performance: 대칭 키 알고리즘은 비대칭 키 알고리즘보다 성능이 좋다.
  • 키 관리: 대칭 키는 비밀 키를 안전하게 관리해야 하며, 비대칭 키는 공개 키와 비밀 키를 따로 관리할 수 있다.

◆ 알고리즘 분류

  • HS (HMAC): 대칭 키 알고리즘으로, 보안이 상대적으로 단순.
  • RS (RSA): 비대칭 키 알고리즘으로, 보다 높은 보안성 제공.
  • ES (ECDSA): 비대칭 키지만, 작은 키 크기로 동일한 보안 제공.

1. 대칭 키 알고리즘 (HS, HMAC)
- HMAC (Hash-based Message Authentication Code): HMAC 알고리즘은 대칭 키 방식으로 비밀 키를 사용하여 해시 값을 생성한다. 이 방법은 데이터 전송 중의 무결성을 검증할 수 있는 서명을 만든다.

  • HS256: HMAC SHA-256 알고리즘 사용.
  • HS384: HMAC SHA-384 알고리즘 사용.
  • HS512: HMAC SHA-512 알고리즘 사용.

이 대칭 키 알고리즘은 같은 비밀 키를 사용하여 서명을 생성하고 검증하기 때문에, 키 관리에 주의해야 한다.

2. 비대칭 키 알고리즘 (RS, ES)
- RS (RSA Signature): RSA는 공개 키 암호 방식을 사용하는 알고리즘으로, 공개 키와 비밀 키 쌍을 사용하여 서명을 생성하고 검증한다. 공개 키로 서명을 검증하고 비밀 키로 서명을 생성한다.

  • RS256: RSA SHA-256 알고리즘을 사용하여 JWT를 서명.
  • RS384: RSA SHA-384 알고리즘을 사용하여 JWT를 서명.
  • RS512: RSA SHA-512 알고리즘을 사용하여 JWT를 서명.

비대칭 키의 장점은 키의 공개와 비밀을 분리할 수 있어 더 안전하다는 것이다.

- ES (ECDSA, Elliptic Curve Digital Signature Algorithm): 타원 곱셈 암호를 사용하는 알고리즘으로, RSA보다 작은 키 크기로 비슷한 수준의 보안을 제공한다.

  • ES256: ECDSA P-256 곡선과 SHA-256을 사용하여 JWT를 서명.
  • ES384: ECDSA P-384 곡선과 SHA-384을 사용하여 JWT를 서명.
  • ES512: ECDSA P-521 곡선과 SHA-512을 사용하여 JWT를 서명.

data

data는 페이로드에 넣을 정보로, 이 정보는 JWT 토큰을 검증한 후 얻을 수 있는 데이터이다.

  • sub: 주로 사용자 ID
  • exp(expiration time): 토큰이 언제 만료될지에 대한 시간 정보 
  • iat(issued at): 토큰이 언제 발행되었는지에 대한 시간 정보
  • aud(audience): 토큰의 대상자 정보
  • Custom data: 원하는 데이터 (ex. role)
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "admin"
}

 

data 딕셔너리에 원하는 정보를 넣고, jwt.encode() 함수로 JWT를 생성하면 되는데, jwt.encode() 함수는 입력된 토큰을 SECRET_KEY와 algorithm을 사용해 검증한다.

jwt.decode(token, SECRET_KEY, algorithms=["HS256"], verify, options, **kwargs)
  • token: 검증할 JWT 토큰 문자열
  • key: 검증에 사용할 키.
  • algorithms: 검증에 사용할 암호 알고리즘의 리스트
  • verify: 토큰의 서명 검증 여부 (기본값: True)
  • options: 추가 검증 옵션을 딕셔너리 형태로 설정 가능 (예: {"verify_exp" : True})
  • kwargs: 알고리즘별로 필요한 추가 인자를 넣을 수 있음

[참고]

가장 빠른 풀스택을 위한 플라스크 & FastAPI

https://wikidocs.net/104836

https://jaeyung1001.tistory.com/entry/Python-relativedelta%ED%95%A8%EC%88%98-timedelta%EC%97%94-%ED%95%9C%EB%8B%AC%EB%B9%BC%EB%8A%94%EA%B2%8C-%EC%99%9C%EC%97%86%EC%9D%84%EA%B9%8C


다음 내용

 

[FastAPI] 고급 인증: 세션

이전 내용 [FastAPI] 고급인증: JWT이전 내용 [FastAPI] 인증과 세션이전 내용 [FastAPI] SQLAlchemy와 CRUD (Depends, db.query)이전 내용 [파이썬] FastAPI - ORM 연동하기(SQLAlchemy)이전 내용 [파이썬] FastAPI - 웹소켓이

puppy-foot-it.tistory.com

728x90
반응형