【问题标题】:Oauth2 authorised routes not working in KtorOauth2 授权路由在 Ktor 中不起作用
【发布时间】:2021-06-21 01:03:50
【问题描述】:

我的目标是获取 GitHub Oauth 令牌以允许访问 GitHub 数据并提供对 Web 应用程序中其他 Ktor 路由的访问控制。 我将 Ktor 示例简化为仅使用 GitHub,并且工作正常并从 GitHub 获取令牌。 但是,从不调用身份验证块中的其他路由。例如,/github/repos 被识别为路由(没有 404),但会通过 GitHub 重新进行身份验证并显示“成功登录”页面,而不是“从 GitHib 获取并显示 repos 信息”。我确信我缺少一些基本的东西,但我很感激地得到了任何帮助。 谢谢,马丁

import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.client.*
import io.ktor.client.engine.apache.*
import io.ktor.html.*
import io.ktor.locations.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.html.*
import java.util.concurrent.TimeUnit


val githubProvider = OAuthServerSettings.OAuth2ServerSettings(
    name = "github",
    authorizeUrl = "https://github.com/login/oauth/authorize",
    accessTokenUrl = "https://github.com/login/oauth/access_token",
    clientId = "**client-id**",
    clientSecret = "**client-secret**",
    defaultScopes = listOf("repo", "user")
)

@Location("/home") class home
@Location("/github/login") class gitHubLogin
@Location("/github/repos") class gitHubRepos
@Location("/github/user") class gitHubUser

@KtorExperimentalLocationsAPI
fun main(args: Array<String>) {
    embeddedServer(Netty, port=8081) {
        module()
    }.start()
}

@KtorExperimentalLocationsAPI
fun Application.mainModule() {
    install(Authentication) {
        oauth("gitHubOAuth") {
            client = HttpClient(Apache)
            providerLookup = { githubProvider }
            urlProvider = { url(gitHubLogin()) }
        }
    }
    install(Locations)
    install(Routing)

    routing {
        get<home> {
            call.respondHtml {
                head {
                    title { +"index page" }
                }
                body {
                    h1 {
                        +"Try to login"
                    }
                    p {
                        a(href = locations.href(gitHubLogin())) {
                            +"Login"
                        }
                    }
                }
            }
        }

        authenticate("gitHubOAuth") {
            location<gitHubLogin>() {
                param("error") {
                    handle {
                        call.loginFailedPage(call.parameters.getAll("error").orEmpty())
                    }
                }
                handle {
                    val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
                    if (principal != null) {
                        call.loggedInSuccessResponse(principal)
                    } else {
                        call.loginPage()
                    }
                }
            }
            get<gitHubUser> {
                call.respondHtml {
                    body {
                        p { +"Get and display user info from GitHib" }
                    }
                }
            }
            get<gitHubRepos> {
                call.respondHtml {
                    body {
                        p { +"Get and display repos info from GitHib" }
                    }
                }
            }
        }
    }
}

@KtorExperimentalLocationsAPI
private suspend fun ApplicationCall.loginPage() {
    respondHtml {
        head {
            title { +"Login with" }
        }
        body {
            h1 {
                +"Login with:"
            }

            p {
                a(href = application.locations.href(gitHubLogin())) {
                    +"GitHub"
                }
            }
        }
    }
}

private suspend fun ApplicationCall.loginFailedPage(errors: List<String>) {
    respondHtml {
        head {
            title { +"Login with" }
        }
        body {
            h1 {
                +"Login error"
            }

            for (e in errors) {
                p {
                    +e
                }
            }
        }
    }
}

@KtorExperimentalLocationsAPI
private suspend fun ApplicationCall.loggedInSuccessResponse(callback: OAuthAccessTokenResponse?) {
    respondHtml {
        head {
            title { +"Logged in" }
        }
        body {
            h1 {
                +"You are logged in"
            }
            p {
                +"Your token is $callback"
            }
            ul {
                li {
                    a(href = locations.href(gitHubUser())) { +"User" }
                }
                li {
                    a(href = locations.href(gitHubRepos())) { +"Repos" }
                }
            }
        }
    }
}

【问题讨论】:

    标签: oauth-2.0 ktor


    【解决方案1】:

    每个客户端对任何受保护(authenticate 块下)路由的请求都会重复 OAuth 过程。您可以使用不受限制的路由来提供对需要令牌的资源的访问。在这些路由的处理程序中,您可以检查客户端是否成功通过身份验证。如果是,那么您可以从会话或与会话关联的令牌存储中获取令牌,并使用它从资源服务器获取数据;如果不是,则将客户端重定向到登录端点或显示错误消息。要使其正常工作,您可以使用 Sessions 来保留有关成功身份验证的知识。

    下面是一个将令牌直接存储在会话中的示例。

    配置Sessions

    data class LoginSession(val token: String)
    
    // ...
    install(Sessions) {
        cookie<LoginSession>("LOGIN_SESSION")
    }
    

    在回调 URL 路由的处理程序中保存会话:

    get("callback") {
        val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
    
        if (principal != null) {
            call.sessions.set(LoginSession(token = principal.accessToken))
        }
    }
    

    使用非限制路由提供对某些资源的访问:

    get("repos") {
        val repos = call.withAuth { session ->
            HttpClient(Apache).get<String>("https://api.github.com/user/repos") {
                header("Authorization", "token ${session.token}")
            }
        }
    
        call.respondText { repos }
    }
    

    ApplicationCall.withAuth 方法只是检查会话是否存在的助手:

    suspend fun <T: Any> ApplicationCall.withAuth(block: suspend (session: LoginSession) -> T): T {
        val session = sessions.get<LoginSession>()
    
        if (session != null) {
            return block(session)
        }
    
        respondRedirect("/login")
        throw Exception("Not authenticated")
    } 
    

    【讨论】:

    • 非常感谢您的回答完全有道理。
    猜你喜欢
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-10-01
    • 1970-01-01
    • 2020-02-05
    相关资源
    最近更新 更多