Skip to main content

인증 기초

인증(Authentication)과 인가(Authorization)는 모든 서비스의 접근 통제를 담당하는 핵심 개념입니다. “이 사람이 누구인가?”(인증)와 “이 사람이 무엇을 할 수 있는가?”(인가)를 구분하는 것부터 시작합니다. LLM API 키 관리, 모델 서빙 엔드포인트 보호, 멀티테넌트 AI 서비스 구축 — 모두 인증/인가의 올바른 설계가 전제됩니다.

학습 목표

  • 인증(Authentication)과 인가(Authorization)의 차이를 명확히 구분한다
  • JWT의 구조와 검증 흐름을 이해하고 보안 주의사항을 설명할 수 있다
  • OAuth 2.0의 주요 Grant 흐름을 이해하고 적합한 상황을 판단한다
  • RBAC 기반의 권한 설계를 ML 팀 조직에 적용할 수 있다

왜 중요한가

AI/ML 서비스에서 인증과 인가는 비용, 보안, 데이터 프라이버시의 교차점입니다. API 키가 유출되면 수백만 원의 과금이 발생할 수 있고, 모델 엔드포인트가 노출되면 누구나 추론 API를 무단으로 사용할 수 있습니다. 학습 데이터에 민감한 정보가 포함되어 있다면, 접근 권한을 올바르게 통제하지 않으면 데이터 유출 사고로 이어집니다. 인증/인가는 개발자뿐 아니라 ML 엔지니어, 데이터 사이언티스트 모두가 이해해야 하는 기본입니다. 모델을 학습시키기 위해 데이터 레이크에 접근할 때, 실험 결과를 MLflow에 기록할 때, 프로덕션 모델을 배포할 때 — 적절한 인증과 최소 권한 원칙이 적용되어야 합니다.

인증 vs 인가

구분인증(Authentication)인가(Authorization)
질문”너는 누구인가?""너는 이것을 할 수 있는가?”
확인 대상신원(Identity)권한(Permission)
실패 시 상태코드401 Unauthorized403 Forbidden
예시로그인, API 키 검증, JWT 토큰 확인관리자 페이지 접근, 모델 삭제 권한
순서먼저 실행인증 이후 실행
401 Unauthorized의 이름이 혼란스럽지만, 실제로는 인증 실패(Unauthenticated)를 의미합니다. 인가 실패(Unauthorized)는 403 Forbidden입니다. 인증이 안 된 상태(토큰 없음/만료)면 401, 인증은 됐지만 권한이 없으면 403입니다.

실무에서 혼동하기 쉬운 지점

# 인증 + 인가 분리 예시 (FastAPI)
from fastapi import Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer()

async def authenticate(credentials: HTTPAuthorizationCredentials = Security(security)):
    """인증: 토큰이 유효한지 확인"""
    user = verify_jwt(credentials.credentials)
    if not user:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return user

async def authorize_admin(user = Depends(authenticate)):
    """인가: 관리자 권한이 있는지 확인"""
    if user.role != "admin":
        raise HTTPException(status_code=403, detail="Admin access required")
    return user

@app.delete("/models/{model_id}", dependencies=[Depends(authorize_admin)])
async def delete_model(model_id: str):
    # 관리자만 모델 삭제 가능
    ...

인증 방식 상세 비교

방식동작 원리장점단점적합 상황
API Key고정 문자열을 헤더에 전송구현 간단, 서버 간 통신 적합세분화된 권한 불가, 유출 시 교체 필요서버 간 통신, 외부 API 호출
JWT서명된 토큰에 사용자 정보 포함무상태, 서버 부하 낮음, 세부 클레임만료 전 강제 취소 어려움, 토큰 크기 큼사용자 인증, 마이크로서비스
OAuth 2.0인가 서버를 통한 위임 인증표준화, 제3자 연동, 세밀한 범위 제어구현 복잡, 러닝 커브 높음제3자 서비스 연동, SSO
mTLS클라이언트-서버 양방향 인증서 검증매우 높은 보안, 양방향 인증인증서 관리 복잡, 설정 어려움제로 트러스트, 서비스 메시
SAMLXML 기반 SSO 프로토콜기업 SSO 표준, 세밀한 속성 전달XML 복잡성, 현대적 개발에 부적합엔터프라이즈 SSO (레거시)

JWT 상세

구조: Header.Payload.Signature

JWT(JSON Web Token)는 점(.)으로 구분된 세 부분으로 구성됩니다.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.     ← Header (Base64URL)
eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJhZG1pbiJ9.  ← Payload (Base64URL)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c     ← Signature
// Header - 서명 알고리즘과 토큰 타입
{
  "alg": "RS256",
  "typ": "JWT"
}

// Payload - 클레임(Claims)
{
  "iss": "https://auth.example.com",   // 발급자
  "sub": "user_123",                    // 대상(사용자 ID)
  "aud": "https://api.example.com",    // 수신자
  "exp": 1735689600,                    // 만료 시각 (Unix timestamp)
  "iat": 1735686000,                    // 발급 시각
  "role": "admin",                      // 커스텀 클레임
  "team": "ml-platform"                 // 커스텀 클레임
}

// Signature - RS256으로 서명
RSASHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  privateKey
)

표준 클레임

클레임이름설명
issIssuer토큰 발급자
subSubject토큰 대상 (보통 사용자 ID)
audAudience토큰 수신자 (API 서버 URL)
expExpiration만료 시각 (필수)
iatIssued At발급 시각
nbfNot Before이 시각 이전에는 유효하지 않음
jtiJWT ID토큰 고유 식별자 (재사용 방지)

서명 알고리즘

알고리즘유형키 관리사용 상황
HS256대칭키 (HMAC)발급자와 검증자가 같은 비밀키 공유단일 서비스, 간단한 구현
RS256비대칭키 (RSA)개인키로 서명, 공개키로 검증마이크로서비스, 분산 검증

검증 흐름

import jwt  # PyJWT 라이브러리
from datetime import datetime, timezone

def verify_jwt(token: str, public_key: str) -> dict:
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],       # 허용 알고리즘 명시
            audience="https://api.example.com",  # aud 검증
            issuer="https://auth.example.com",   # iss 검증
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Token has expired")
    except jwt.InvalidAudienceError:
        raise HTTPException(401, "Invalid audience")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Invalid token")
alg:none 공격 방지: 반드시 algorithms 파라미터에 허용할 알고리즘을 명시적으로 지정하세요. algorithms 없이 검증하면 공격자가 헤더의 algnone으로 바꿔 서명 없이 토큰을 위조할 수 있습니다. 또한 HS256RS256을 혼동하는 공격(공개키를 대칭키로 사용)도 방지해야 합니다.

OAuth 2.0 흐름

OAuth 2.0은 제3자 애플리케이션이 사용자 리소스에 접근하도록 위임하는 프레임워크입니다.

Authorization Code Grant (가장 안전하고 보편적)

1

1. 인가 요청

클라이언트가 사용자를 인가 서버의 로그인 페이지로 리다이렉트합니다.
GET /authorize?response_type=code
  &client_id=app_123
  &redirect_uri=https://myapp.com/callback
  &scope=read:models write:experiments
  &state=random_csrf_token
2

2. 사용자 인증 및 동의

사용자가 로그인하고 요청된 권한 범위(scope)에 동의합니다.
3

3. 인가 코드 수신

인가 서버가 redirect_uri로 인가 코드(authorization code)를 전달합니다.
GET /callback?code=auth_code_xyz&state=random_csrf_token
4

4. 토큰 교환

클라이언트가 인가 코드를 서버 사이드에서 액세스 토큰으로 교환합니다.
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=auth_code_xyz
&client_id=app_123
&client_secret=secret_456
&redirect_uri=https://myapp.com/callback
5

5. API 접근

발급받은 액세스 토큰으로 리소스 서버의 API를 호출합니다.
GET /api/models
Authorization: Bearer access_token_abc

기타 Grant 유형

Grant 유형적합 상황비고
Client Credentials서버 간 통신 (사용자 없이)ML 파이프라인 서비스 계정
Device Code입력 제한 디바이스 (TV, CLI)CLI 도구 인증 (gh auth login)
ImplicitSPA에서 직접 토큰 수신보안 취약으로 폐기됨, PKCE 사용 권장
PKCEAuthorization Code + 코드 검증SPA, 모바일 앱의 표준
Client Credentials Grant는 AI/ML에서 매우 자주 사용됩니다. 학습 파이프라인이 데이터 레이크에 접근하거나, CI/CD가 모델 레지스트리에 배포할 때, 사용자 개입 없이 서비스 계정으로 인증합니다.

RBAC 설계

RBAC(Role-Based Access Control)는 역할에 권한을 매핑하여 접근을 통제하는 모델입니다.

기본 역할 정의

역할설명권한
viewer읽기 전용데이터 조회, 대시보드 열람
developer개발/실험실험 생성, 모델 학습, 리소스 사용
admin관리자사용자 관리, 설정 변경, 리소스 할당
service-account자동화 서비스특정 API만 호출 (최소 권한)

ML 팀 역할 매핑

역할팀 직군주요 권한
data-engineer데이터 엔지니어데이터 파이프라인 CRUD, 데이터 레이크 읽기/쓰기
ml-engineerML 엔지니어모델 학습/평가, 실험 관리, GPU 리소스 요청
mlopsMLOps 엔지니어모델 배포/롤백, 인프라 관리, 모니터링 설정
pm프로덕트 매니저대시보드 열람, 실험 결과 조회, 보고서 생성

권한 매트릭스 테이블

리소스/액션viewerdata-engineerml-engineermlopsadmin
실험 결과 조회OOOOO
데이터셋 업로드XOXXO
모델 학습 실행XXOXO
모델 프로덕션 배포XXXOO
GPU 할당량 변경XXXOO
사용자/역할 관리XXXXO
API 키 발급/폐기XXXOO

멀티테넌시

멀티테넌시(Multi-tenancy)는 하나의 서비스가 여러 조직(테넌트)을 격리하여 서비스하는 아키텍처입니다.

테넌트 격리 전략

전략격리 수준비용복잡성적합 상황
DB 분리가장 높음높음낮음엔터프라이즈, 규제 산업
스키마 분리높음중간중간중규모 SaaS
행 수준(Row-Level)보통낮음높음대규모 SaaS, 비용 민감

AI 서비스에서의 멀티테넌시

# 행 수준 격리 예시 - 모든 쿼리에 tenant_id 필터 적용
@app.get("/models")
async def list_models(user = Depends(authenticate)):
    tenant_id = user.tenant_id
    models = db.query(Model).filter(Model.tenant_id == tenant_id).all()
    return models

# API 키에 테넌트 정보 포함
# sk-tenant1-abc123... → tenant_id: "tenant1"
# sk-tenant2-def456... → tenant_id: "tenant2"
AI 서비스에서 멀티테넌시는 비용 격리도 중요합니다. 테넌트별 토큰 사용량 추적, GPU 할당량 관리, Rate Limit 분리가 필요합니다. 한 테넌트의 과도한 사용이 다른 테넌트에 영향을 주면 안 됩니다 (“noisy neighbor” 문제).

AI/ML 인증 패턴

LLM API 키 관리

# 환경변수로 관리 (절대 하드코딩 금지)
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# 프로젝트별 API 키 분리 권장
# OPENAI_API_KEY_DEV    → 개발/실험용 (낮은 Rate Limit)
# OPENAI_API_KEY_PROD   → 프로덕션용 (높은 Rate Limit)
# OPENAI_API_KEY_BATCH  → 배치 처리용 (비용 최적화)

모델 서빙 엔드포인트 인증

# FastAPI + JWT로 모델 서빙 엔드포인트 보호
@app.post("/v1/predict")
async def predict(
    request: PredictRequest,
    user = Depends(authenticate),
):
    # 사용자별 Rate Limit 적용
    check_rate_limit(user.id, user.tier)

    # 사용량 기록 (과금용)
    log_usage(user.tenant_id, input_tokens=len(request.text))

    result = model.predict(request.text)
    return {"prediction": result}

학습 파이프라인 서비스 계정

# Kubernetes ServiceAccount + RBAC
apiVersion: v1
kind: ServiceAccount
metadata:
  name: training-pipeline
  namespace: ml-platform
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: training-role
  namespace: ml-platform
rules:
  - apiGroups: [""]
    resources: ["pods", "secrets"]
    verbs: ["get", "list", "create"]
  - apiGroups: ["batch"]
    resources: ["jobs"]
    verbs: ["get", "list", "create", "delete"]

HuggingFace 토큰

# HuggingFace Hub 인증
# 읽기 전용 토큰: hf_xxxReadOnlyxxx (공개 모델 다운로드)
# 쓰기 토큰: hf_xxxWriteTokenxxx (모델 업로드, 프라이빗 접근)

# CLI 로그인
huggingface-cli login --token $HF_TOKEN

# Python에서 사용
from huggingface_hub import login
login(token=os.environ["HF_TOKEN"])

# 환경변수로 자동 인증
# HF_TOKEN=hf_xxx... (자동으로 인식)
Access Token: 짧은 수명(15분1시간). API 접근에 사용. 만료되면 새로 발급받아야 합니다. Refresh Token: 긴 수명(7일30일). Access Token 재발급에 사용. 서버에 안전하게 저장. 이 패턴을 사용하면 Access Token이 탈취되어도 피해 기간을 최소화할 수 있습니다. Refresh Token이 탈취되면 즉시 무효화(revoke)해야 합니다.
API 키: 서버 간 통신, 외부 API 호출, 간단한 인증이 필요할 때. OpenAI, HuggingFace가 이 방식. JWT: 사용자별 인증이 필요하고, 토큰에 사용자 정보(역할, 권한)를 포함해야 할 때. 함께 사용: API 키로 서비스를 식별하고, JWT로 해당 서비스 내의 사용자를 인증하는 이중 구조도 흔합니다.
JWT는 **무상태(Stateless)**이므로 서버가 세션을 저장하지 않아도 됩니다. 하지만 만료 전에 강제 로그아웃(토큰 취소)이 어렵다는 단점이 있습니다. 이를 보완하기 위해: (1) 짧은 만료 시간 + Refresh Token, (2) 블랙리스트(Redis), (3) 토큰 버전 관리(JTI 추적) 등의 방법을 사용합니다.
전통적 모델: 네트워크 경계(VPN) 안에 있으면 신뢰 → 내부 위협에 취약. Zero Trust: “절대 신뢰하지 말고, 항상 검증하라” → 모든 요청에 인증/인가 적용. 구현 요소: mTLS(서비스 간), JWT(사용자), 컨텍스트 기반 접근 제어(디바이스, 위치, 시간). AI/ML 인프라에서도 학습 노드 간 통신, 모델 레지스트리 접근 등에 Zero Trust 원칙이 점점 적용되고 있습니다.

체크리스트

  • 인증(Authentication)과 인가(Authorization)의 차이를 명확히 설명할 수 있다
  • JWT의 세 구성요소(Header, Payload, Signature)와 주요 클레임을 이해한다
  • HS256과 RS256의 차이와 적합한 사용 상황을 안다
  • OAuth 2.0 Authorization Code Grant 흐름을 단계별로 설명할 수 있다
  • Client Credentials Grant가 ML 파이프라인에서 어떻게 사용되는지 이해한다
  • RBAC로 ML 팀의 역할별 권한을 설계할 수 있다
  • API 키를 환경변수로 관리하고, 용도별로 분리해야 하는 이유를 안다
  • 401과 403 상태코드의 차이를 실무 상황에 적용하여 설명할 수 있다

다음 문서