이전 내용
[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
다음 내용
[FastAPI] 고급 인증: 세션
이전 내용 [FastAPI] 고급인증: JWT이전 내용 [FastAPI] 인증과 세션이전 내용 [FastAPI] SQLAlchemy와 CRUD (Depends, db.query)이전 내용 [파이썬] FastAPI - ORM 연동하기(SQLAlchemy)이전 내용 [파이썬] FastAPI - 웹소켓이
puppy-foot-it.tistory.com
'[파이썬 Projects] > <파이썬 웹개발>' 카테고리의 다른 글
[파이썬] FastAPI - 비동기 처리 (asynchronous processing) (0) | 2025.05.08 |
---|---|
[파이썬] FastAPI - 고급 인증: 세션 (0) | 2025.05.07 |
[파이썬] FastAPI - 인증과 세션 (0) | 2025.05.01 |
[파이썬] FastAPI - SQLAlchemy와 CRUD (Depends, db.query) (0) | 2025.05.01 |
[파이썬] FastAPI - ORM 연동하기(SQLAlchemy) (0) | 2025.04.28 |