API Key Authentication#
API Key Authentication은 마이크로서비스 아키텍처(MSA)에서 보안을 위해 사용되는 중요한 인증 방식이다.
API Key Authentication은 클라이언트가 API에 접근할 때 고유한 식별자(API 키)를 사용하여 인증하는 방식이다. 이 키는 서버에서 생성하여 클라이언트에게 제공되며, 클라이언트는 API 요청 시 이 키를 포함시켜 자신의 신원을 증명한다.
API Key Authentication은 구현이 간단하고 사용하기 쉽다는 장점이 있지만, 보안 측면에서는 제한적이다. 따라서 중요한 데이터나 높은 보안이 요구되는 서비스에는 OAuth2나 JWT와 같은 더 강력한 인증 방식을 고려해야 한다.
작동 방식#
- 서버가 클라이언트에게 고유한 API 키를 발급한다.
- 클라이언트는 API 요청 시 이 키를 헤더, 쿼리 파라미터 또는 요청 본문에 포함시킨다.
- HTTP 헤더:
X-API-Key: your_api_key
- 쿼리 파라미터:
https://api.example.com/data?api_key=your_api_key
- 서버는 요청에 포함된 API 키를 확인하여 클라이언트를 인증한다.
구현 방법#
- API 키 생성: 서버에서 무작위의 문자열로 API 키를 생성한다.
- 키 저장: 생성된 키를 안전한 데이터베이스에 저장한다.
- 키 전달: 안전한 채널을 통해 클라이언트에게 키를 전달한다.
- 요청 처리: API 요청 시 키를 확인하고 유효한 경우에만 요청을 처리한다.
- 구현이 간단하다.
- 클라이언트 측에서 사용하기 쉽다.
- 서버 측에서 API 사용량을 모니터링하고 제어하기 용이하다.
- API 키가 노출되면 보안에 취약해진다.
- 키가 탈취되면 즉시 대응하기 어렵다.
- 사용자별 권한 관리가 제한적이다.
보안 강화 방법#
- HTTPS 사용: 모든 API 통신에 HTTPS를 적용하여 키 전송을 암호화한다.
- 키 순환: 정기적으로 API 키를 갱신한다.
- 접근 제한: IP 화이트리스팅 등을 통해 키 사용을 제한한다.
사용 사례#
- 공개 API: 날씨 정보, 지도 서비스 등의 공개 API에서 사용량 제한을 위해 사용된다.
- B2B 서비스: 기업 간 데이터 교환 시 간단한 인증 방식으로 사용된다.
- 개발자 포털: API 사용량 추적 및 과금을 위해 사용된다.
구현 예시#
레이트 리미팅과 권한 관리를 포함한 API Key 인증 시스템 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| class EnhancedAPIKeyAuthService {
constructor(redisClient, dbClient) {
this.redis = redisClient;
this.db = dbClient;
this.rateLimitWindow = 3600; // 1시간
}
async validateRequest(apiKey, resource, method) {
// 1. API 키 유효성 검증
const keyData = await this.validateApiKey(apiKey);
if (!keyData) {
throw new UnauthorizedError('Invalid API key');
}
// 2. 레이트 리미팅 검사
const isWithinLimit = await this.checkRateLimit(apiKey);
if (!isWithinLimit) {
throw new RateLimitExceededError('Rate limit exceeded');
}
// 3. 권한 검증
const hasPermission = await this.checkPermission(
keyData.permissions,
resource,
method
);
if (!hasPermission) {
throw new ForbiddenError('Insufficient permissions');
}
// 4. 사용 기록
await this.logApiUsage(apiKey, resource, method);
return keyData;
}
async validateApiKey(apiKey) {
// 캐시 확인
const cached = await this.redis.get(`api_key:${apiKey}`);
if (cached) {
return JSON.parse(cached);
}
// DB에서 키 정보 조회
const keyData = await this.db.apiKeys.findOne({
key: apiKey,
isActive: true
});
if (keyData) {
// 캐시에 저장
await this.redis.setex(
`api_key:${apiKey}`,
3600,
JSON.stringify(keyData)
);
}
return keyData;
}
async checkRateLimit(apiKey) {
const key = `rate_limit:${apiKey}`;
const current = await this.redis.incr(key);
if (current === 1) {
await this.redis.expire(key, this.rateLimitWindow);
}
return current <= this.getRateLimit(apiKey);
}
}
|
참고 및 출처#