首页 > 解决方案 > 如何从当前用户那里获取角色?

问题描述

我正在尝试使用 JWT 实现 AuthN/AuthZ,如下所示:

class MainVerticle : CoroutineVerticle() {

  private suspend fun initConfig(): JsonObject {
    val yamlConfigOpts = ConfigStoreOptions()
      .setFormat("yaml")
      .setType("file")
      .setConfig(JsonObject().put("path", "config.yaml"))

    val configRetrieverOpts = ConfigRetrieverOptions()
      .addStore(yamlConfigOpts)

    val configRetriever = ConfigRetriever.create(vertx, configRetrieverOpts)

    return configRetriever.config.await()
  }


  private suspend fun createJwtAuth(client: WebClient, config: JsonObject): JWTAuth? {

    val issuer = config.getJsonObject("jwt").getString("issuer")

    // derive JWKS uri from Keycloak issuer URI
    val jwksUri = URI.create("${issuer}/protocol/openid-connect/certs")

    // The exception will be caught above
    val res = client.get(jwksUri.host, jwksUri.path).send().await()

    return res.bodyAsJsonObject()?.let {
      val keys = it.getJsonArray("keys")

      val jwtOpt = JWTOptions()
      jwtOpt.issuer = issuer


      // configure JWTAuth
      val jwtAuthOptions = JWTAuthOptions()

      jwtAuthOptions.jwks = (keys.list as List<Map<String, *>>)
        .map { json -> JsonObject(json) }
        .map { json -> json.put("permissionsClaimKey", "realm_access/roles") }
      jwtAuthOptions.jwtOptions = jwtOpt



      JWTAuth.create(vertx, jwtAuthOptions)
    } ?: throw AuthenticationException("Can not receive the token")

  }

  private fun createRoutes(router: Router, jwtAuth: JWTAuth): Unit {

    router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth))

    router.route("/api/greet").handler {

      val token = it.request().getHeader(HttpHeaders.AUTHORIZATION).substring("Bearer ".length)

      jwtAuth.authenticate(JsonObject().put("jwt", token))
        .onSuccess { user ->
          val res = it.response()
          res.putHeader("content-type", "text/plain")

          // Write to the response and end it
          res.end("I am interests path")
        }
        .onFailure { err -> it.response().setStatusCode(403).end(err.message) }


    }

  }


  private suspend fun server(router: Router): HttpServer {
    val server = vertx.createHttpServer()

    return server.requestHandler(router)
      .listen(8080)
      .onSuccess {
        println("HTTP server started on port ${it.actualPort()}")
      }
      .onFailure {
        println("Failed to start the server. Reason ${it.message}")
      }
      .await()
  }


  override suspend fun start() {

    val config = initConfig()
    val webClient = WebClient.create(vertx)
    val router = Router.router(vertx)

    createJwtAuth(webClient, config)?.let {
      createRoutes(router, it)
      server(router)

    }
  }
}

/api/greet路由处理程序中,我想读出用户的声明,例如角色或名称。但不幸的是,这就是我所拥有的:

在此处输入图像描述

问题是,如何阅读用户的声明?

我使用 Keycloak 作为身份提供者和 Vertx 版本4.0.0.CR1

更新

我已更改为OAuth2 身份验证提供程序并调整代码如下:

class MainVerticle : CoroutineVerticle() {

  private suspend fun createJwtAuth(): OAuth2Auth =

    KeycloakAuth.discover(
      vertx,
      OAuth2Options()
        .setFlow(OAuth2FlowType.AUTH_CODE)
        .setClientID("svc")
        .setClientSecret("secret")
        .setSite("https://oic.example.io/auth/realms/vertx")
    ).await()


  private fun createRoutes(router: Router, auth: OAuth2Auth): Unit {

    val oauth2 = OAuth2AuthHandler.create(vertx, auth)

    router.route("/api/*").handler(oauth2)

    router.route("/api/greet").handler {

      println(it.user().principal().getString("preferred_username"))

      val res = it.response()
      res.putHeader("content-type", "text/plain")

      // Write to the response and end it
      res.end("I am interests path")


    }

  }


  private suspend fun server(router: Router): HttpServer {
    val server = vertx.createHttpServer()

    return server.requestHandler(router)
      .listen(8080)
      .onSuccess {
        println("HTTP server started on port ${it.actualPort()}")
      }
      .onFailure {
        println("Failed to start the server. Reason ${it.message}")
      }
      .await()
  }


  override suspend fun start() {

    val router = Router.router(vertx)


    createRoutes(router, createJwtAuth())
    server(router)

  }

}

线

println(it.user().principal().getString("preferred_username"))

打印null用户名。我究竟做错了什么?

标签: javakotlinkeycloakvert.x

解决方案


当您使用当前的最新版本时,让我解释一下新 API 中的变化。在 4.0.0 中,我们进行了拆分authnauthz因此在您的示例中,您已经authn正确执行并获得了一个User对象实例。

现在您要提取权限,因为您JWTAuth正在采取“低”级路径,而如果您要使用OAuth2Auth某些东西则没有必要(例如加载密钥等)。

现在您有了一个用户对象,您需要一个authz提取对象。为此,我将以 java API 为例,但在 Kotlin 中应该非常相似:

// First create a JWTAuthorization object
JWTAuthorization.create("realm_access/roles")
  .getAuthorizations(user)
  .onSuccess(success -> {
    // The authorizations have been successfully extracted from the user
    // Now you can perform any kind of checks

    if (PermissionBasedAuthorization.create("write").match(user)) {
      // ... User is allowed to write...
    }
  });

因此,权限是从您传递给提取器的attributes下方提取的。claimKey简而言之,attributes是由框架生成的生成和/或解码/验证的数据,principal而是为创建用户而提供的源数据。区别很重要,因为现在用户对象可以用于服务器验证和客户端请求。

这也意味着在保持源不变的情况下,不存在解码会覆盖重要数据(如到期日期等)的风险。


推荐阅读