API Key 인증
필수 의존성: io.ktor:ktor-server-auth, io.ktor:ktor-server-auth-api-key
API Key 인증은 클라이언트가 요청의 일부로(일반적으로 헤더에) 비밀 키를 전달하는 간단한 인증 방식입니다. 이 키는 식별자이자 인증 메커니즘 역할을 동시에 수행합니다.
Ktor를 사용하면 라우트를 보호하고 클라이언트 요청을 검증하기 위해 API Key 인증을 사용할 수 있습니다.
Ktor의 인증에 대한 일반적인 정보는 Ktor 서버의 인증 및 인가 섹션에서 확인할 수 있습니다.
API Key는 비밀로 유지되어야 하며 안전하게 전송되어야 합니다. 전송 중인 API Key를 보호하기 위해 HTTPS/TLS를 사용하는 것이 권장됩니다.
의존성 추가
API Key 인증을 활성화하려면 빌드 스크립트에 ktor-server-auth 및 ktor-server-auth-api-key 아티팩트를 추가하세요.
implementation("io.ktor:ktor-server-auth-api-key:$ktor_version")
implementation("io.ktor:ktor-server-auth:$ktor_version")implementation "io.ktor:ktor-server-auth-api-key:$ktor_version"
implementation "io.ktor:ktor-server-auth:$ktor_version"<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-auth-api-key-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-auth</artifactId>
<version>${ktor_version}</version>
</dependency>API Key 인증 흐름
API Key 인증 흐름은 다음과 같습니다:
- 클라이언트가 서버 애플리케이션의 특정 라우트에 헤더(일반적으로
X-API-Key)를 포함한 API Key와 함께 요청을 보냅니다. - 서버는 커스텀 검증 로직을 사용하여 API Key를 확인합니다.
- 키가 유효하면 서버는 요청된 콘텐츠로 응답합니다. 키가 유효하지 않거나 누락된 경우, 서버는
401 Unauthorized상태로 응답합니다.
API Key 인증 설치
apiKey 인증 프로바이더를 설치하려면, install(Authentication) 블록 내에서 apiKey 함수를 호출하세요.
import io.ktor.server.application.*
import io.ktor.server.auth.*
// ...
install(Authentication) {
apiKey {
// API Key 인증 구성
}
}필요한 경우 특정 라우트를 인증하는 데 사용할 수 있는 프로바이더 이름을 지정할 수 있습니다.
API Key 인증 구성
이 섹션에서는 apiKey 인증 프로바이더의 구체적인 구성 방법을 살펴봅니다.
Ktor에서 다양한 인증 프로바이더를 구성하는 방법은 인증 구성을 참조하세요.
1단계: API Key 프로바이더 구성
apiKey 인증 프로바이더는 ApiKeyAuthenticationProvider.Config 클래스를 통해 설정을 노출합니다. 아래 예제에서는 다음 설정이 지정되었습니다:
validate함수는 요청에서 추출된 API Key를 수신하며, 인증 성공 시Principal을 반환하고 인증 실패 시null을 반환합니다.
최소한의 예제는 다음과 같습니다:
data class AppPrincipal(val key: String) : Principal
install(Authentication) {
apiKey {
validate { keyFromHeader ->
val expectedApiKey = "this-is-expected-key"
keyFromHeader
.takeIf { it == expectedApiKey }
?.let { AppPrincipal(it) }
}
}
}키 위치 커스터마이징
기본적으로 apiKey 프로바이더는 X-API-Key 헤더에서 API Key를 찾습니다.
headerName을 사용하여 커스텀 헤더를 지정할 수 있습니다:
apiKey("api-key-header") {
headerName = "X-Secret-Key"
validate { key ->
// ...
}
}2단계: API Key 검증
검증 로직은 애플리케이션의 요구 사항에 따라 달라집니다. 다음은 일반적인 접근 방식입니다:
정적 키 비교
단순한 경우, 미리 정의된 키와 비교할 수 있습니다:
apiKey {
validate { keyFromHeader ->
val expectedApiKey = environment.config.property("api.key").getString()
keyFromHeader
.takeIf { it == expectedApiKey }
?.let { AppPrincipal(it) }
}
}민감한 API Key는 소스 코드가 아닌 구성 파일이나 환경 변수에 저장하세요.
데이터베이스 조회
여러 API Key가 있는 경우, 데이터베이스를 통해 검증하세요:
apiKey {
validate { keyFromHeader ->
// 데이터베이스에서 키 조회
val user = database.findUserByApiKey(keyFromHeader)
user?.let { UserIdPrincipal(it.username) }
}
}다중 검증 기준
복잡한 검증 로직을 구현할 수 있습니다:
apiKey {
validate { keyFromHeader ->
val apiKey = database.findApiKey(keyFromHeader)
// 키가 존재하고, 활성 상태이며, 만료되지 않았는지 확인
if (apiKey != null &&
apiKey.isActive &&
apiKey.expiresAt > Clock.System.now()
) {
UserIdPrincipal(apiKey.userId)
} else {
null
}
}
}3단계: 챌린지 구성
challenge 함수를 사용하여 인증 실패 시 전송되는 응답을 커스터마이징할 수 있습니다:
apiKey {
validate { key ->
// 검증 로직
}
challenge { defaultScheme, realm ->
call.respond(
HttpStatusCode.Unauthorized,
"Invalid or missing API key"
)
}
}4단계: 특정 리소스 보호
apiKey 프로바이더를 구성한 후, authenticate 함수를 사용하여 애플리케이션의 특정 리소스를 보호할 수 있습니다. 인증에 성공하면 라우트 핸들러 내부에서 call.principal 함수를 사용하여 인증된 프린시펄(principal)을 가져올 수 있습니다.
routing {
authenticate {
get("/") {
val principal = call.principal<AppPrincipal>()!!
call.respondText("Hello, authenticated client! Your key: ${principal.key}")
}
}
}API Key 인증 예제
다음은 API Key 인증의 전체적인 최소 예제입니다:
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
data class AppPrincipal(val key: String) : Principal
fun Application.module() {
val expectedApiKey = "this-is-expected-key"
install(Authentication) {
apiKey {
validate { keyFromHeader ->
keyFromHeader
.takeIf { it == expectedApiKey }
?.let { AppPrincipal(it) }
}
}
}
routing {
authenticate {
get("/") {
val principal = call.principal<AppPrincipal>()!!
call.respondText("Key: ${principal.key}")
}
}
}
}베스트 프랙티스
API Key 인증을 구현할 때 다음 베스트 프랙티스를 고려하세요:
- HTTPS 사용: 가로채기를 방지하기 위해 항상 HTTPS를 통해 API Key를 전송하세요.
- 안전하게 저장: 소스 코드에 API Key를 하드코딩하지 마세요. 환경 변수나 보안 구성 관리 도구를 사용하세요.
- 키 로테이션: 주기적으로 API Key를 교체(rotation)하는 메커니즘을 구현하세요.
- 속도 제한(Rate limiting): 남용을 방지하기 위해 API Key 인증과 속도 제한을 결합하세요.
- 로깅: 보안 모니터링을 위해 인증 실패를 로깅하되, 실제 API Key는 절대 로깅하지 마세요.
- 키 형식: API Key에는 암호학적으로 안전한 무작위 문자열(예: UUID 또는 base64로 인코딩된 무작위 바이트)을 사용하세요.
- 다중 키: 다른 애플리케이션이나 목적을 위해 사용자당 여러 API Key를 지원하는 것을 고려하세요.
- 만료: 보안 강화를 위해 키 만료 기능을 구현하세요.
