콘텐츠로 이동

토큰 인증 Flow

Access Token & Refresh Token

graph TD
    A[Client] -->|1. 로그인 요청| B[API Server]
    B -->|2. 토큰 생성| C[JWT]
    C -->|3. Access Token 발급| B
    C -->|4. Refresh Token 발급| B
    B -->|5. Refresh Token 저장| D[(Database)]
    B -->|6. 토큰 반환| A

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#dfd,stroke:#333,stroke-width:2px
    style D fill:#fdd,stroke:#333,stroke-width:2px

API 요청 인증 Flow

sequenceDiagram
    participant Client
    participant API
    participant JWT
    participant DB

    Client->>API: API 요청
    Note over Client,API: Authorization: Bearer {access_token}

    API->>JWT: Access Token 검증

    alt 유효한 토큰
        JWT-->>API: 검증 성공
        API->>DB: 요청 처리
        API-->>Client: 응답
    else 만료된 토큰
        JWT-->>API: 토큰 만료
        API-->>Client: 401 Unauthorized
    end

Refresh Token을 이용한 재인증 Flow

sequenceDiagram
    participant Client
    participant API
    participant JWT
    participant DB

    Note over Client: Access Token 만료 감지

    Client->>API: POST /v1/users/refresh
    Note over Client,API: Authorization: Bearer {refresh_token}

    API->>JWT: Refresh Token 검증
    API->>DB: Refresh Token 유효성 확인

    alt 유효한 Refresh Token
        API->>JWT: 새로운 토큰 쌍 생성
        API->>DB: 새 Refresh Token 저장
        API-->>Client: 새로운 Access Token + Refresh Token
    else 만료된 Refresh Token
        API-->>Client: 401 Unauthorized
        Note over Client: 로그인 페이지로 리다이렉트
    end

토큰 만료 시나리오

stateDiagram-v2
    [*] --> ValidTokens: 로그인 성공
    ValidTokens --> AccessExpired: Access Token 만료 (1시간)
    AccessExpired --> ValidTokens: Refresh Token으로 재발급
    AccessExpired --> LoginRequired: Refresh 실패
    ValidTokens --> LoginRequired: Refresh Token 만료 (7일)
    LoginRequired --> ValidTokens: 재로그인
    LoginRequired --> [*]: 로그아웃

토큰 구조

Access Token Payload

{
  "sub": "user_id",
  "domain": "example.com",
  "symId": "0x123...",
  "role": "DOMAIN_USER",
  "iat": 1516239022,
  "exp": 1516242622  // 1시간 후
}

Refresh Token Payload

{
  "sub": "user_id",
  "type": "refresh",
  "iat": 1516239022,
  "exp": 1516843822  // 7일 후
}

보안 고려사항

  1. Access Token
  2. 짧은 유효기간 (1시간)
  3. 모든 API 요청에 사용
  4. 클라이언트 메모리에만 저장
  5. JWT 형식으로 자체 검증 가능

  6. Refresh Token

  7. 긴 유효기간 (7일)
  8. DB에 저장하여 무효화 가능
  9. 안전한 저장소에 보관
  10. 재발급 시에만 사용

  11. 토큰 저장

  12. Access Token: 메모리 (JavaScript 변수)
  13. Refresh Token: HttpOnly 쿠키
  14. LocalStorage 사용 금지

Access Token 상세

구조

graph TD
    A[Access Token] --> B[Header]
    A --> C[Payload]
    A --> D[Signature]

    B --> B1[알고리즘: HS256]
    B --> B2[토큰 타입: JWT]

    C --> C1[사용자 정보]
    C --> C2[권한 정보]
    C --> C3[만료 시간]

    D --> D1[서명]

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#dfd,stroke:#333,stroke-width:2px
    style D fill:#fdd,stroke:#333,stroke-width:2px

JWT 형식

// Header
{
  "alg": "HS256",  // 서명 알고리즘
  "typ": "JWT"     // 토큰 타입
}

// Payload
{
  "sub": "user_id",          // 사용자 ID
  "domain": "example.com",   // 도메인
  "symId": "0x123...",      // 심볼 ID
  "role": "DOMAIN_USER",    // 권한
  "iat": 1516239022,        // 발급 시간
  "exp": 1516242622        // 만료 시간 (1시간)
}

// Signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Access Token 검증 프로세스

sequenceDiagram
    participant Client
    participant Guard
    participant JWT
    participant BlackList

    Client->>Guard: API 요청
    Note over Client,Guard: Bearer Token in Header

    rect rgb(200, 220, 250)
        Guard->>Guard: 1. Bearer Token 추출
        Guard->>BlackList: 2. 블랙리스트 확인
        Guard->>JWT: 3. 서명 검증
        Guard->>JWT: 4. 만료 시간 확인
        Guard->>Guard: 5. 페이로드 추출
    end

    alt 검증 성공
        Guard-->>Client: 요청 처리 진행
    else 블랙리스트에 있는 토큰
        Guard-->>Client: 403 Forbidden
    else 서명 불일치
        Guard-->>Client: 401 Unauthorized
    else 만료된 토큰
        Guard-->>Client: 401 Unauthorized
    end

검증 단계 설명

  1. Bearer Token 추출
  2. Authorization 헤더에서 Bearer 토큰 추출
  3. 형식: Bearer <token>

  4. 블랙리스트 확인

  5. 로그아웃된 토큰인지 확인
  6. Redis 또는 메모리에서 확인

  7. 서명 검증

  8. JWT 서명 알고리즘으로 검증
  9. 토큰 변조 여부 확인

  10. 만료 시간 확인

  11. exp 클레임 검증
  12. 현재 시간과 비교

  13. 페이로드 추출

  14. 검증된 토큰에서 사용자 정보 추출
  15. Request에 사용자 정보 주입

Access Token 관련 에러 처리

에러 종류와 응답

// 1. 토큰 누락
{
  "statusCode": 401,
  "message": "Missing access token",
  "error": "Unauthorized"
}

// 2. 잘못된 토큰 형식
{
  "statusCode": 401,
  "message": "Invalid token format",
  "error": "Unauthorized"
}

// 3. 만료된 토큰
{
  "statusCode": 401,
  "message": "Token has expired",
  "error": "Unauthorized"
}

// 4. 블랙리스트에 있는 토큰
{
  "statusCode": 403,
  "message": "Token has been revoked",
  "error": "Forbidden"
}

// 5. 권한 부족
{
  "statusCode": 403,
  "message": "Insufficient permissions",
  "error": "Forbidden"
}

에러 처리 Flow

flowchart TD
    A[API 요청] --> B{토큰 존재?}
    B -->|No| C[401: Missing Token]
    B -->|Yes| D{토큰 형식?}
    D -->|Invalid| E[401: Invalid Format]
    D -->|Valid| F{만료 확인}
    F -->|Expired| G[401: Expired]
    F -->|Valid| H{블랙리스트}
    H -->|Listed| I[403: Revoked]
    H -->|Not Listed| J{권한 확인}
    J -->|Invalid| K[403: Insufficient]
    J -->|Valid| L[요청 처리]

Access Token 갱신 정책

갱신 시나리오

stateDiagram-v2
    [*] --> Active: 토큰 발급
    Active --> GracePeriod: 만료 임박 (50분 경과)
    GracePeriod --> Renewed: 자동 갱신
    GracePeriod --> Expired: 갱신 실패
    Expired --> Renewed: Refresh Token 사용
    Renewed --> Active: 새 토큰 발급
    Expired --> [*]: 재로그인 필요

갱신 전략

  1. 선제적 갱신
  2. 만료 10분 전 자동 갱신 시도
  3. 백그라운드에서 처리
  4. 사용자 경험 유지

  5. 강제 갱신

  6. 401 응답 시 즉시 갱신 시도
  7. Refresh Token 사용
  8. 실패 시 로그인 페이지 이동

  9. 병렬 갱신 방지

  10. 갱신 중복 요청 방지
  11. 갱신 요청 큐잉
  12. 레이스 컨디션 방지

  13. 토큰 재사용 방지

  14. 갱신된 토큰은 즉시 사용
  15. 이전 토큰 블랙리스트 등록
  16. 보안 강화