Ktor Server에서의 인증 및 인가
필수 의존성: io.ktor:ktor-server-auth
Ktor는 인증 및 인가를 처리하기 위해 Authentication 플러그인을 제공합니다. 일반적인 사용 시나리오에는 사용자 로그인, 특정 리소스에 대한 접근 권한 부여, 당사자 간의 안전한 정보 전송 등이 포함됩니다. 또한 Authentication을 세션(Sessions)과 함께 사용하여 요청 간에 사용자의 정보를 유지할 수도 있습니다.
클라이언트 측에서 Ktor는 인증 및 인가 처리를 위한 Authentication 플러그인을 제공합니다.
지원되는 인증 유형
Ktor는 다음과 같은 인증 및 인가 스킴을 지원합니다:
HTTP 인증
HTTP는 접근 제어 및 인증을 위한 일반적인 프레임워크를 제공합니다. Ktor에서는 다음과 같은 HTTP 인증 스킴을 사용할 수 있습니다:
- Basic -
Base64인코딩을 사용하여 사용자 이름과 비밀번호를 제공합니다. HTTPS와 함께 사용하지 않는 한 일반적으로 권장되지 않습니다. - Digest - 사용자 이름과 비밀번호에 해시 함수를 적용하여 암호화된 형태로 사용자 자격 증명을 통신하는 인증 방법입니다.
- Bearer - 전달자 토큰(bearer tokens)이라는 보안 토큰이 포함된 인증 스킴입니다. Bearer 인증 스킴은 OAuth 또는 JWT의 일부로 사용되지만, Bearer 토큰 인증을 위한 커스텀 로직을 제공할 수도 있습니다.
- API Key - 클라이언트가 헤더에 비밀 키를 전달하는 간단한 인증 방법입니다.
폼 기반 인증
폼 기반(Form-based) 인증은 웹 폼을 사용하여 크리덴셜 정보를 수집하고 사용자를 인증합니다.
JSON Web Tokens (JWT)
JSON Web Token은 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 개방형 표준입니다. 인가를 위해 JSON Web Token을 사용할 수 있습니다. 사용자가 로그인하면 각 요청에 토큰이 포함되어, 해당 토큰으로 허용된 리소스에 사용자가 접근할 수 있게 합니다. Ktor에서는 jwt 인증을 사용하여 토큰을 검증하고 그 안에 포함된 클레임(claims)을 확인할 수 있습니다.
LDAP
LDAP는 디렉터리 서비스 인증에 사용되는 개방형 크로스 플랫폼 프로토콜입니다. Ktor는 지정된 LDAP 서버에 대해 사용자 크리덴셜을 인증하는 ldapAuthenticate 함수를 제공합니다.
OAuth
OAuth는 API 접근 보안을 위한 개방형 표준입니다. Ktor의 oauth 프로바이더를 사용하면 Google, Facebook, Twitter 등과 같은 외부 프로바이더를 사용하여 인증을 구현할 수 있습니다.
세션
세션(Sessions)은 서로 다른 HTTP 요청 간에 데이터를 유지하는 메커니즘을 제공합니다. 일반적인 사용 사례로는 로그인한 사용자의 ID, 장바구니 내용 저장 또는 클라이언트에 사용자 기본 설정 유지 등이 있습니다. Ktor에서 이미 연결된 세션이 있는 사용자는 session 프로바이더를 사용하여 인증될 수 있습니다. 자세한 방법은 Ktor Server의 세션 인증에서 확인하세요.
커스텀
Ktor는 인증 및 인가 동작을 커스터마이징하는 두 가지 방법을 제공합니다:
- 커스텀 인증 프로바이더를 사용합니다.
- 커스텀 플러그인을 사용하여 인가 로직을 구현합니다. 예를 들어,
AuthenticationChecked훅(hook)을 사용하여 접근 권한을 확인할 수 있습니다. 자세한 내용은 custom-plugin-authorization 예제를 참조하십시오.
의존성 추가
Authentication을 사용하려면 빌드 스크립트에 ktor-server-auth 아티팩트를 포함해야 합니다:
JWT 및 LDAP와 같은 일부 인증 프로바이더는 추가 아티팩트가 필요하다는 점에 유의하세요.
Authentication 설치
애플리케이션에 Authentication 플러그인을 설치하려면, 지정된
install 함수에 전달하십시오. 아래의 코드 스니펫은 Authentication을 설치하는 방법을 보여줍니다 ... - ...
embeddedServer함수 호출 내부에서. - ...
Application클래스의 확장 함수인 명시적으로 정의된module내부에서.
Authentication 설정
Authentication 설치 후, 다음과 같이 Authentication을 설정하고 사용할 수 있습니다:
1단계: 인증 프로바이더 선택
basic, digest, 또는 form과 같은 특정 인증 프로바이더를 사용하려면 install 블록 내부에서 해당 함수를 호출해야 합니다. 예를 들어, 기본 인증(basic authentication)을 사용하려면 .basic() 함수를 호출합니다:
import io.ktor.server.application.*
import io.ktor.server.auth.*
// ...
install(Authentication) {
basic {
// 기본 인증(basic authentication) 설정
}
}이 함수 내부에서 해당 프로바이더에 특정한 설정을 구성할 수 있습니다.
기본 제공 프로바이더가 요구 사항에 맞지 않는 경우, 커스텀 인증 프로바이더를 구현할 수 있습니다.
2단계: 프로바이더 이름 지정
특정 프로바이더를 사용하기 위한 함수에서는 선택적으로 프로바이더 이름을 지정할 수 있습니다. 아래의 코드 샘플은 basic 및 form 프로바이더를 각각 "auth-basic" 및 "auth-form"이라는 이름으로 설치합니다:
install(Authentication) {
basic("auth-basic") {
// 기본 인증 설정
}
form("auth-form") {
// 폼 인증 설정
}
// ...
}이 이름들은 나중에 서로 다른 프로바이더를 사용하여 서로 다른 라우트를 인증하는 데 사용될 수 있습니다.
프로바이더 이름은 고유해야 하며, 이름이 없는 프로바이더는 하나만 정의할 수 있습니다.
3단계: 프로바이더 설정
각 프로바이더 유형은 고유한 설정을 가집니다. 예를 들어, BasicAuthenticationProvider.Config 클래스는 .basic() 함수에 대한 옵션을 제공합니다. 이 클래스의 핵심 함수는 사용자 이름과 비밀번호의 유효성을 검사하는 책임을 지는 validate()입니다. 다음 코드 예제는 그 사용법을 보여줍니다:
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}validate() 함수가 어떻게 작동하는지 이해하려면 두 가지 용어를 알아야 합니다:
- 프린시펄(principal)은 인증될 수 있는 개체입니다: 사용자, 컴퓨터, 서비스 등. Ktor에서 다양한 인증 프로바이더는 서로 다른 프린시펄을 사용할 수 있습니다. 예를 들어,
basic,digest,form프로바이더는UserIdPrincipal을 인증하는 반면,jwt프로바이더는JWTPrincipal을 검증합니다.커스텀 프린시펄을 생성할 수도 있습니다. 이는 다음과 같은 경우에 유용할 수 있습니다:
- 크리덴셜(credential)은 서버가 프린시펄을 인증하기 위한 속성 집합입니다: 사용자/비밀번호 쌍, API 키 등. 예를 들어,
basic및form프로바이더는UserPasswordCredential을 사용하여 사용자 이름과 비밀번호를 검증하는 반면,jwt는JWTCredential을 검증합니다.
따라서 validate() 함수는 지정된 크리덴셜을 확인하고 인증에 성공하면 프린시펄 Any를 반환하고, 인증에 실패하면 null을 반환합니다.
특정 기준에 따라 인증을 건너뛰려면
skipWhen()을 사용하십시오. 예를 들어, 세션이 이미 존재하는 경우basic인증을 건너뛸 수 있습니다:kotlinbasic { skipWhen { call -> call.sessions.get<UserSession>() != null } }
4단계: 특정 리소스 보호
마지막 단계는 애플리케이션의 특정 리소스를 보호하는 것입니다. 이는 authenticate() 함수를 사용하여 수행할 수 있습니다. 이 함수는 두 개의 선택적 매개변수를 받습니다:
중첩된 라우트를 인증하는 데 사용되는 프로바이더 이름. 아래 코드 스니펫은 auth-basic 이라는 이름의 프로바이더를 사용하여
/login및/orders라우트를 보호합니다:kotlinrouting { authenticate("auth-basic") { get("/login") { // ... } get("/orders") { // ... } } get("/") { // ... } }중첩된 인증 프로바이더를 해결하는 데 사용되는 전략. 이 전략은
AuthenticationStrategy열거형 값으로 표현됩니다.예를 들어, 클라이언트는
AuthenticationStrategy.Required전략으로 등록된 모든 프로바이더에 대해 인증 데이터를 제공해야 합니다. 아래 코드 스니펫에서는 세션 인증을 통과한 사용자만이 기본 인증을 사용하여/admin라우트에 접근을 시도할 수 있습니다:kotlinrouting { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }
전체 예제는 auth-form-session-nested를 참조하십시오.
5단계: 라우트 핸들러 내에서 프린시펄 가져오기
인증에 성공하면 call.principal() 함수를 사용하여 라우트 핸들러 내부에서 인증된 프린시펄을 검색할 수 있습니다. 이 함수는 설정된 인증 프로바이더가 반환하는 특정 프린시펄 유형을 인자로 받습니다. 다음 예제에서 call.principal()은 UserIdPrincipal을 얻고 인증된 사용자의 이름을 가져오는 데 사용됩니다.
routing {
authenticate("auth-basic") {
get("/") {
call.respondText("Hello, ${call.principal<UserIdPrincipal>()?.name}!")
}
}
}세션 인증을 사용하는 경우, 프린시펄은 세션 데이터를 저장하는 데이터 클래스일 수 있습니다. 따라서 이 데이터 클래스를 call.principal()에 전달해야 합니다:
authenticate("auth-session") {
get("/hello") {
val userSession = call.principal<UserSession>()
}
}중첩된 인증 프로바이더의 경우, call.principal()에 프로바이더 이름을 전달하여 원하는 프로바이더의 프린시펄을 가져올 수 있습니다.
아래 예제에서는 최상위 세션 프로바이더의 프린시펄을 가져오기 위해 "auth-session" 값이 전달됩니다:
authenticate("auth-session", strategy = AuthenticationStrategy.Required) {
authenticate("auth-basic", strategy = AuthenticationStrategy.Required) {
get("/admin") {
val userSession = call.principal<UserSession>("auth-session")
}
}
}커스텀 인증 프로바이더
기본 제공 프로바이더가 요구 사항에 맞지 않는 경우, provider() 함수를 사용하여 커스텀 인증 로직을 구현하십시오:
provider("custom") {
authenticate { context ->
val exampleHeader = context.call.request.headers["Example-Header"]
if (exampleHeader == null) {
val cause = AuthenticationFailedCause.Error("No example header found")
context.challenge(key = this, cause) { challenge, call ->
call.respondText("Challenge")
challenge.complete()
}
}
}
}위의 예제에서 provider("custom") 함수는 나중에 authenticate("custom")를 사용하여 라우트에 적용할 수 있는 이름이 지정된 인증 프로바이더를 등록합니다.
프로바이더 내부의 authenticate {} 블록은 모든 들어오는 요청에 대해 실행되며, 컨텍스트 객체를 통해 인증 프로세스에 대한 전체 제어권을 부여합니다. 여기에는 현재 호출(context.call)에 대한 접근과 헤더, 매개변수 또는 기타 요청 데이터를 검사할 수 있는 기능이 포함됩니다.
DynamicProviderConfig 클래스에서 제공하는 옵션을 사용하여 추가적인 프로바이더 동작을 구성할 수 있습니다.
