Oauth2 授权路由在 Ktor 中不起作用

Posted

技术标签:

【中文标题】Oauth2 授权路由在 Ktor 中不起作用【英文标题】:Oauth2 authorised routes not working in 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" 
                
            
        
    

【问题讨论】:

【参考方案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")
 

【讨论】:

非常感谢您的回答完全有道理。

以上是关于Oauth2 授权路由在 Ktor 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在 Ktor 中向路由添加拦截器不起作用

Spring Boot OAuth2 CORS 在版本 2.2.1 中不起作用

身份验证在spring boot 1.5.2和Oauth2中不起作用

自定义授权属性在 WebAPI 中不起作用

Google OAuth2 - 令牌的交换访问代码 - 不起作用

为啥 React 路由在组件中不起作用?