feat: OAuth 2.0 백엔드 시스템 구현 완료
Phase 1 & 2 완료: - 프로젝트 기본 구조 설정 - Docker Compose 환경 구성 (MongoDB, Redis, Backend, Frontend) - FastAPI 기반 OAuth 2.0 백엔드 구현 주요 기능: - JWT 기반 인증 시스템 - 3단계 권한 체계 (System Admin/Group Admin/User) - 사용자 관리 CRUD API - 애플리케이션 관리 CRUD API - OAuth 2.0 Authorization Code Flow - Refresh Token 관리 - 인증 히스토리 추적 API 엔드포인트: - /auth/* - 인증 관련 (register, login, logout, refresh) - /users/* - 사용자 관리 - /applications/* - 애플리케이션 관리 - /oauth/* - OAuth 2.0 플로우 보안 기능: - bcrypt 비밀번호 해싱 - JWT 토큰 인증 - CORS 설정 - Rate limiting 준비 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
201
oauth/backend/app/services/token_service.py
Normal file
201
oauth/backend/app/services/token_service.py
Normal file
@ -0,0 +1,201 @@
|
||||
"""Token management service"""
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from bson import ObjectId
|
||||
from app.utils.database import get_database
|
||||
from app.utils.security import create_access_token, create_refresh_token, decode_token
|
||||
from app.models.user import Token
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TokenService:
|
||||
"""Service for handling token operations"""
|
||||
|
||||
@staticmethod
|
||||
async def create_tokens(user_id: str, user_data: Dict[str, Any]) -> Token:
|
||||
"""Create access and refresh tokens for a user"""
|
||||
# Create access token
|
||||
access_token = create_access_token(
|
||||
data={
|
||||
"sub": user_id,
|
||||
"username": user_data.get("username"),
|
||||
"role": user_data.get("role"),
|
||||
"email": user_data.get("email")
|
||||
}
|
||||
)
|
||||
|
||||
# Create refresh token
|
||||
refresh_token = create_refresh_token(
|
||||
data={
|
||||
"sub": user_id,
|
||||
"username": user_data.get("username")
|
||||
}
|
||||
)
|
||||
|
||||
# Store refresh token in database
|
||||
await TokenService.store_refresh_token(user_id, refresh_token)
|
||||
|
||||
return Token(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
token_type="bearer"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def store_refresh_token(user_id: str, token: str):
|
||||
"""Store refresh token in database"""
|
||||
db = get_database()
|
||||
|
||||
from app.config import settings
|
||||
expires_at = datetime.utcnow() + timedelta(
|
||||
days=settings.jwt_refresh_token_expire_days
|
||||
)
|
||||
|
||||
token_doc = {
|
||||
"token": token,
|
||||
"user_id": user_id,
|
||||
"created_at": datetime.utcnow(),
|
||||
"expires_at": expires_at,
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
await db.refresh_tokens.insert_one(token_doc)
|
||||
|
||||
@staticmethod
|
||||
async def verify_refresh_token(token: str) -> Optional[str]:
|
||||
"""Verify refresh token and return user_id"""
|
||||
db = get_database()
|
||||
|
||||
# Decode token
|
||||
payload = decode_token(token)
|
||||
if not payload or payload.get("type") != "refresh":
|
||||
return None
|
||||
|
||||
# Check if token exists in database
|
||||
token_doc = await db.refresh_tokens.find_one({
|
||||
"token": token,
|
||||
"is_active": True
|
||||
})
|
||||
|
||||
if not token_doc:
|
||||
return None
|
||||
|
||||
# Check if token is expired
|
||||
if token_doc["expires_at"] < datetime.utcnow():
|
||||
# Mark token as inactive
|
||||
await db.refresh_tokens.update_one(
|
||||
{"_id": token_doc["_id"]},
|
||||
{"$set": {"is_active": False}}
|
||||
)
|
||||
return None
|
||||
|
||||
return payload.get("sub")
|
||||
|
||||
@staticmethod
|
||||
async def revoke_refresh_token(token: str):
|
||||
"""Revoke a refresh token"""
|
||||
db = get_database()
|
||||
|
||||
await db.refresh_tokens.update_one(
|
||||
{"token": token},
|
||||
{"$set": {"is_active": False}}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def revoke_all_user_tokens(user_id: str):
|
||||
"""Revoke all refresh tokens for a user"""
|
||||
db = get_database()
|
||||
|
||||
await db.refresh_tokens.update_many(
|
||||
{"user_id": user_id},
|
||||
{"$set": {"is_active": False}}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def create_authorization_code(
|
||||
user_id: str,
|
||||
client_id: str,
|
||||
redirect_uri: str,
|
||||
scope: str,
|
||||
state: Optional[str] = None
|
||||
) -> str:
|
||||
"""Create and store authorization code"""
|
||||
db = get_database()
|
||||
|
||||
code = secrets.token_urlsafe(32)
|
||||
expires_at = datetime.utcnow() + timedelta(minutes=10) # Code valid for 10 minutes
|
||||
|
||||
code_doc = {
|
||||
"code": code,
|
||||
"user_id": user_id,
|
||||
"client_id": client_id,
|
||||
"redirect_uri": redirect_uri,
|
||||
"scope": scope,
|
||||
"state": state,
|
||||
"created_at": datetime.utcnow(),
|
||||
"expires_at": expires_at,
|
||||
"used": False
|
||||
}
|
||||
|
||||
await db.authorization_codes.insert_one(code_doc)
|
||||
|
||||
return code
|
||||
|
||||
@staticmethod
|
||||
async def exchange_authorization_code(
|
||||
code: str,
|
||||
client_id: str,
|
||||
client_secret: str,
|
||||
redirect_uri: str
|
||||
) -> Optional[Token]:
|
||||
"""Exchange authorization code for tokens"""
|
||||
db = get_database()
|
||||
|
||||
# Find and validate code
|
||||
code_doc = await db.authorization_codes.find_one({
|
||||
"code": code,
|
||||
"client_id": client_id,
|
||||
"redirect_uri": redirect_uri,
|
||||
"used": False
|
||||
})
|
||||
|
||||
if not code_doc:
|
||||
return None
|
||||
|
||||
# Check if code is expired
|
||||
if code_doc["expires_at"] < datetime.utcnow():
|
||||
return None
|
||||
|
||||
# Validate client secret
|
||||
app = await db.applications.find_one({
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret
|
||||
})
|
||||
|
||||
if not app:
|
||||
return None
|
||||
|
||||
# Mark code as used
|
||||
await db.authorization_codes.update_one(
|
||||
{"_id": code_doc["_id"]},
|
||||
{"$set": {"used": True}}
|
||||
)
|
||||
|
||||
# Get user
|
||||
user = await db.users.find_one({"_id": ObjectId(code_doc["user_id"])})
|
||||
if not user:
|
||||
return None
|
||||
|
||||
# Create tokens
|
||||
return await TokenService.create_tokens(
|
||||
str(user["_id"]),
|
||||
{
|
||||
"username": user["username"],
|
||||
"role": user["role"],
|
||||
"email": user["email"]
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user