토큰 인증 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일 후
}
보안 고려사항
- Access Token
- 짧은 유효기간 (1시간)
- 모든 API 요청에 사용
- 클라이언트 메모리에만 저장
-
JWT 형식으로 자체 검증 가능
-
Refresh Token
- 긴 유효기간 (7일)
- DB에 저장하여 무효화 가능
- 안전한 저장소에 보관
-
재발급 시에만 사용
-
토큰 저장
- Access Token: 메모리 (JavaScript 변수)
- Refresh Token: HttpOnly 쿠키
- 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
검증 단계 설명
- Bearer Token 추출
- Authorization 헤더에서 Bearer 토큰 추출
-
형식:
Bearer <token> -
블랙리스트 확인
- 로그아웃된 토큰인지 확인
-
Redis 또는 메모리에서 확인
-
서명 검증
- JWT 서명 알고리즘으로 검증
-
토큰 변조 여부 확인
-
만료 시간 확인
- exp 클레임 검증
-
현재 시간과 비교
-
페이로드 추출
- 검증된 토큰에서 사용자 정보 추출
- 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 --> [*]: 재로그인 필요
갱신 전략
- 선제적 갱신
- 만료 10분 전 자동 갱신 시도
- 백그라운드에서 처리
-
사용자 경험 유지
-
강제 갱신
- 401 응답 시 즉시 갱신 시도
- Refresh Token 사용
-
실패 시 로그인 페이지 이동
-
병렬 갱신 방지
- 갱신 중복 요청 방지
- 갱신 요청 큐잉
-
레이스 컨디션 방지
-
토큰 재사용 방지
- 갱신된 토큰은 즉시 사용
- 이전 토큰 블랙리스트 등록
- 보안 강화