kotlin - 在 Ktor : Is it supposed to work like this? 中使用 KeyCloak 进行 OAuth

标签 kotlin authentication oauth-2.0 keycloak ktor

我尝试通过 Ktor Web 服务器中的 Keycloak 设置有效的 Oauth2 授权。预期的流程是从 Web 服务器向 keycloak 发送请求并登录给定的 UI,然后 Keycloak 发回可用于接收 token 的代码。赞 here
首先,我根据 Ktor 文档中的示例进行了操作。 Oauth它运行良好,直到我必须接收 token ,然后它才给我 HTTP 状态 401。即使 curl 命令正常工作。然后我尝试了一个我在 GitHub 上找到的示例项目,我设法通过构建自己的 HTTP 请求并将其发送到 Keycloak 服务器以接收 token 来使其工作,但是它应该像这样工作吗?
我对此有多个问题。

  • 这个函数是否应该同时处理授权和获取 token ?
     authenticate(keycloakOAuth) {
         get("/oauth") {
             val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
    
             call.respondText("Access Token = ${principal?.accessToken}")
         }
     }
    
  • 我认为我的配置是正确的,因为我可以收到授权,而不是 token 。
    const val KEYCLOAK_ADDRESS = "**"
    
    val keycloakProvider = OAuthServerSettings.OAuth2ServerSettings(
    name = "keycloak",
    authorizeUrl = "$KEYCLOAK_ADDRESS/auth/realms/production/protocol/openid-connect/auth",
    accessTokenUrl = "$KEYCLOAK_ADDRESS/auth/realms/production/protocol/openid-connect/token",
    clientId = "**",
    clientSecret = "**",
    accessTokenRequiresBasicAuth = false,
    requestMethod = HttpMethod.Post, // must POST to token endpoint
    defaultScopes = listOf("roles")
    )
    const val keycloakOAuth = "keycloakOAuth"
    
     install(Authentication) {
         oauth(keycloakOAuth) {
         client = HttpClient(Apache)
         providerLookup = { keycloakProvider }
         urlProvider = { "http://localhost:8080/token" }
     }
    }
    
  • 我用一个内置的 HTTP 请求创建了这个/token 路由,这个路由设法获取了 token ,但感觉就像一个黑客。
    get("/token"){
     var grantType = "authorization_code"
     val code = call.request.queryParameters["code"]
     val requestBody = "grant_type=${grantType}&" +
             "client_id=${keycloakProvider.clientId}&" +
             "client_secret=${keycloakProvider.clientSecret}&" +
             "code=${code.toString()}&" +
             "redirect_uri=http://localhost:8080/token"
    
     val tokenResponse = httpClient.post<HttpResponse>(keycloakProvider.accessTokenUrl) {
         headers {
             append("Content-Type","application/x-www-form-urlencoded")
         }
         body = requestBody
     }
     call.respondText("Access Token = ${tokenResponse.readText()}")
    }
    

  • TL;DR:我可以通过 Keycloak 登录,但是尝试获取 access_token 会给我 401。ktor 中的身份验证功能是否也应该处理它?

    最佳答案

    第一个问题的答案:如果该路由对应于 urlProvider 中返回的重定向 URI,它将用于两者 lambda 。
    整体流程如下:

  • 用户在浏览器中打开 http://localhost:7777/login(authenticate 下的任何路由)
  • Ktor 重定向到 authorizeUrl传递必要的参数
  • 用户通过 Keycloak UI 登录
  • Keycloak 将用户重定向到 urlProvider 提供的重定向 URI。获取访问 token 所需的 lambda 传递参数
  • Ktor 向 token URL 发出请求并执行与重定向 URI 对应的路由处理程序(示例中为 http://localhost:7777/callback)。
  • 在处理程序中,您可以访问 OAuthAccessTokenResponse具有访问 token 、刷新 token 和从 Keycloak 返回的任何其他参数的属性的对象。

  • 这是工作示例的代码:
    val provider = OAuthServerSettings.OAuth2ServerSettings(
        name = "keycloak",
        authorizeUrl = "http://localhost:8080/auth/realms/master/protocol/openid-connect/auth",
        accessTokenUrl = "http://localhost:8080/auth/realms/$realm/protocol/openid-connect/token",
        clientId = clientId,
        clientSecret = clientSecret,
        requestMethod = HttpMethod.Post // The GET HTTP method is not supported for this provider
    )
    
    fun main() {
        embeddedServer(Netty, port = 7777) {
            install(Authentication) {
                oauth("keycloak_oauth") {
                    client = HttpClient(Apache)
                    providerLookup = { provider }
                    // The URL should match "Valid Redirect URIs" pattern in Keycloak client settings
                    urlProvider = { "http://localhost:7777/callback" }
                }
            }
    
            routing {
                authenticate("keycloak_oauth") {
                    get("login") {
                        // The user will be redirected to authorizeUrl first
                    }
    
                    route("/callback") {
                        // This handler will be executed after making a request to a provider's token URL.
                        handle {
                            val principal = call.authentication.principal<OAuthAccessTokenResponse>()
    
                            if (principal != null) {
                                val response = principal as OAuthAccessTokenResponse.OAuth2
                                call.respondText { "Access token: ${response.accessToken}" }
                            } else {
                                call.respondText { "NO principal" }
                            }
                        }
                    }
                }
            }
        }.start(wait = false)
    }
    

    关于kotlin - 在 Ktor : Is it supposed to work like this? 中使用 KeyCloak 进行 OAuth,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66364961/

    相关文章:

    api - 了解 Oauth2

    android - Retrofit 2 with 协程调用适配器工厂取消请求

    security - 告诉用户用户名不存在是不是不好的做法? http ://en. wikipedia.org

    node.js - 使用passport.js在node.js中进行身份验证后重定向到上一页

    authentication - Mojolicious 基本登录

    javascript - Facebook用户认证流程

    OAuth 1 在线签名测试器?

    javafx - 设置 cellFormat 时 makeeditable 不起作用

    android - 在Android响应式(Reactive)编程中,如何在应用过滤器后拆分流对象数据?

    kotlin - 如何在Kotlin中将Long拆分为两个Int?