Ktor + Cognito로 OAuth2 (OpenID Connect) 인증

16801 단어 cognitoKotlinoauth2
Ktor가 1.0이 되고 나서 오랫동안 이미 1.3의 preview까지 발전하고 있으므로 개인개발 앱으로 사용해 보았습니다.
인증 수단으로 Cognito의 OpenID Connect 인증을 사용했는데 미묘하게 맞지 않아서 공유합니다.
Google의 Auth2 인증 및 Github 예제는 공식에 나와 있습니다. 하지만, 간선 Ktor 초보자에게는 알기 어려운 쓰는 방법이었으므로 보다 상세한 예를 사용해 실장하는 것으로 알기 쉽게 했습니다.
이번 구현은 GitHub에 공개 하고 있습니다.

대상



OAuth2, OpenID Connect에 대해 대부분 이해합니다.
Ktor에서 OAuth2를 사용하면 어떻게됩니까? 한 사람

전제



Kotlin 1.3.50
Ktor 1.2.4

종속성 추가



빠른 시작에서 아무 것도 옵션을 선택하지 않은 상태에서 아래 두 줄을 추가합니다.

build.gradle
    // https://mvnrepository.com/artifact/io.ktor/ktor-auth
    compile "io.ktor:ktor-auth:$ktor_version"

    // https://mvnrepository.com/artifact/io.ktor/ktor-client-apache
    compile "io.ktor:ktor-client-apache:$ktor_version"

ktor-auth는 OAuth2 인증을 사용하기 때문에 ktor-client-apache는 인증 흐름 중에 Cognito와 통신하는 데 사용됩니다.

Cognito



Cognito 튜토리얼 참조.
기본적으로는 기본 설정대로 문제 없다.
app-client를 만든 후 callback url을 설정합니다.

clientId,client secret를 확인한다.


서버 측 (OAuth2 관점에서 클라이언트)



다음은 전체 코드입니다.

Application.kt
fun Application.module(testing: Boolean = false) {
    install(Authentication) {
        oauth(COGNITO) {
            client = HttpClient()
            providerLookup = {
                val domain = getEnv("cognito.domain")
                OAuthServerSettings.OAuth2ServerSettings(
                    name = "cognito",
                    authorizeUrl = "$domain/oauth2/authorize",
                    accessTokenUrl = "$domain/oauth2/token",
                    requestMethod = HttpMethod.Post,
                    clientId = getEnv("cognito.clientId"),
                    clientSecret = getEnv("cognito.clientSecret")
                )
            }
            urlProvider = { "http://localhost:8080/login" }
        }
    }
    routing {
        authenticate(COGNITO) {
            get("/login") {
                val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
                if (principal == null) {
                    call.respondRedirect("http://localhost:8080/")
                } else {
                    val domain = getEnv("cognito.domain")
                    val client = HttpClient()
                    val result = client.get<String>("$domain/oauth2/userInfo") {
                        header("Authorization", "Bearer ${principal.accessToken}")
                    }
                    call.respondHtml {
                        body {
                            h1 { +"you are login." }
                            a { +result }
                        }
                    }
                }
            }
        }

        get("/") {
            call.respondHtml {
                head {
                    title { +"Login with" }
                }
                body {
                    h1 { +"Login with:" }
                    a(href = "/login") { +"cognito" }
                }
            }
        }
    }
}

상세하게 나누어 보겠습니다.

OAuth2 설정



Application.kt
    install(Authentication) {
        // 中略
    }
const val COGNITO = "cognito"

먼저 Ktor에게 인증 기능 사용을 선언합니다.

Application.kt
        oauth(COGNITO) {
            client = HttpClient()
            providerLookup = { 
                // 中略 
            }
            urlProvider = { "http://localhost:8080/login" }
        }      

인증 모듈 중에도 OAuth2 기능을 사용합니다. oauth 이외에도 basic, form을 사용할 수 있습니다.
  • providerLookup
    Authorization Server 설정
  • urlProvider
    callbackURL

  • Application.kt
                providerLookup = {
                    val domain = getEnv("cognito.domain")
                    OAuthServerSettings.OAuth2ServerSettings(
                        name = "cognito",
                        authorizeUrl = "$domain/oauth2/authorize",
                        accessTokenUrl = "$domain/oauth2/token",
                        requestMethod = HttpMethod.Post,
                        clientId = getEnv("cognito.clientId"),
                        clientSecret = getEnv("cognito.clientSecret")
                    )
                }
    

    Authorization Server를 설정합니다. 기본적으로 인수명대로입니다만 requestMethod는 default에서는 Get가 설정s라고 하고 있어 이번 사용하는 Cognito는 token 리퀘스트시에 Post method 지정 때문에 명시적으로 requestMethod를 지정합니다.

    이것으로 OAuth2 설정이 완료되었습니다.

    Routing 설정



    Application.kt
    routing {
        authenticate(COGNITO) {
            get("/login") {
                // 中略
            }
        }
    
        get("/") {
            // 中略
        }
    }
    

    인증을 하고 싶은 장소에 방금 설정한 인증명을 이용해 authenticate로 wrap 합니다./login에 액세스했을 때에는 인증되어 있는지 확인하여 OAuth2 인증을 합니다. 뭐든지 dsl 기법이 되어 있는 것이 Ktor같네요.

    Applicaiton.kt
                    val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
                    if (principal == null) {
                        call.respondRedirect("http://localhost:8080/")
                    } else {
                        val domain = getEnv("cognito.domain")
                        val client = HttpClient()
                        val result = client.get<String>("$domain/oauth2/userInfo") {
                            header("Authorization", "Bearer ${principal.accessToken}")
                        }
                        call.respondHtml {
                            body {
                                h1 { +"you are login." }
                                a { +result }
                            }
                        }
                    }
    
    

    나중에 route 내에서 자동으로 얻은 principal을 사용하여 실제로 사용할 수 있는지 userInfo 엔드 포인트에 요청을 던져 보겠습니다.

    잘하면 위와 같이 사용자 정보를 얻을 수 있습니다.

    요약



    이번에는 access token을 취득했을 뿐이므로 실제로 사용자 인증으로 사용하려면 jwt를 사용해야하지만 내용 복잡화한다고 판단했기 때문에 그만두었습니다. Spring Boot의 OAuth2 인증 또한 시도했지만 그보다 복잡성이 적습니다.
    후에 HTML DSL이 좋다! . 이번과 같이 간단한 HTML을 필요로 하는 구현을 코드 위에서 바삭바삭하게 한다고 하는 것은 프로토타이핑이 보다 쉬워지고 있는 것을 느낍니다. 실제로 개인 개발에서 사용하고 있습니다만 화면 흐름의 확인에 관해서는 이것을 사용해 어느 정도 할 수 있으므로 마음에 듭니다.

    좋은 웹페이지 즐겨찾기