【问题标题】:OAuth with KeyCloak in Ktor : Is it supposed to work like this?Ktor 中带有 KeyCloak 的 OAuth:它应该像这样工作吗?
【发布时间】:2021-05-27 14:28:29
【问题描述】:

我尝试通过 Keycloak 在 Ktor Web 服务器中设置有效的 Oauth2 授权。预期的流程是从 Web 服务器向 keycloak 发送请求并登录给定的 UI,然后 Keycloak 发送回可用于接收令牌的代码。赞here

首先我是根据 Ktor 文档中的示例进行的。 Oauth 它运行良好,直到它到达我必须接收令牌的地步,然后它只给了我 HTTP 状态 401。即使 curl 命令正常工作。然后我尝试了一个在 GitHub 上找到的示例项目,我设法通过构建自己的 HTTP 请求并将其发送到 Keycloak 服务器以接收令牌来使其工作,但它应该像这样工作吗?

我对此有多个问题。

  1. 这个函数是否应该同时处理授权和获取令牌?

     authenticate(keycloakOAuth) {
         get("/oauth") {
             val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
    
             call.respondText("Access Token = ${principal?.accessToken}")
         }
     }
    
  2. 我认为我的配置是正确的,因为我可以收到授权,而不是令牌。

    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" }
     }
    }
    
  3. 我使用构建的 HTTP 请求创建了这个 /token 路由,这个路由设法获取了令牌,但感觉就像是 hack。

    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 中的身份验证函数是否也应该处理这个问题?

【问题讨论】:

    标签: kotlin authentication oauth-2.0 keycloak ktor


    【解决方案1】:

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

    整体流程如下:

    1. 用户在浏览器中打开 http://localhost:7777/login(authenticate 下的任意路由)
    2. Ktor 重定向到 authorizeUrl 并传递必要的参数
    3. 用户通过 Keycloak UI 登录
    4. Keycloak 将用户重定向到 urlProvider lambda 提供的重定向 URI,传递获取访问令牌所需的参数
    5. Ktor 向令牌 URL 发出请求并执行与重定向 URI 对应的路由处理程序(示例中为 http://localhost:7777/callback)。
    6. 在处理程序中,您可以访问 OAuthAccessTokenResponse 对象,该对象具有访问令牌、刷新令牌和从 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)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多