【问题标题】:How can I create a custom directive for Akka-Http 2 by nesting existing Directives?如何通过嵌套现有指令为 Akka-Http 2 创建自定义指令?
【发布时间】:2016-03-28 08:10:44
【问题描述】:

我正在尝试创建一个自定义指令,以后可以使用它来验证用户角色,如下所示:

val route = put & authorize(ADMIN) { 
              // do sth
            }

val route = put {
              authorize(ADMIN) {
                //do sth
              }
            }

。这是我到目前为止得到的:

  def authorize(role: String): Directive0 = {
    extractToken { t: String =>
      validateToken(t) {
        extractRoles(t) { roles: Seq[String] =>
          validate(roles.contains(role), s"User does not have the required role")
        }
      }
    }
  }

  def extractRoles(token: String): Directive1[Seq[String]] = {
    token match {
      case JsonWebToken(header, claimsSet, signature) => {
        val decoded = decodeJWT(token)
        decoded match {
          case Some(_) => provide(extractRoleCodesFromJWT(decoded.get))
          case None => provide(Seq())
        }
      }
      case x =>
        provide(Seq())
    }
  }

  def validateToken(token: String): Directive0 = validate(validateJWT(token), INVALID_TOKEN_MESSAGE)

  def extractToken: Directive1[Option[String]] = extractToken | extractHeader

  def extractCookie: Directive1[Option[String]] = optionalCookie(JWT_COOKIE_NAME).map(_.map(_.value))

  def extractHeader: Directive1[Option[String]] = optionalHeaderValueByName(JWT_HEADER_NAME)

除了我想稍后使用的实际 Directive0(授权)之外,所有编译都很好。最里面的行validate(...) 显示编译错误,提示“server.Directive0 类型的表达式不符合预期的 server.Route 类型”

如何正确嵌套其他指令以形成我的授权指令? 或者我可以以其他方式连接它们,而不是嵌套? 不幸的是,关于自定义指令的文档非常少。

[更新]

感谢 Java Anto 的指针,这就是我想出的。

 def authorize(role: String): Directive0 = {
    extractToken.flatMap(validateToken)
                .flatMap(extractRoles)
                .flatMap(roles => validate(roles.contains(role), MISSING_ROLE_MESSAGE))
  }

这可以编译,并有望完成我正确测试它的技巧。

【问题讨论】:

  • 您可以使用现有指令上的'map'和'flatMap'函数来组成新的指令。
  • @Java Anto:谢谢,这是一个很好的指导。我还不能回答自己,所以我会更新问题以包含解决方案。
  • 很高兴它有帮助!对于清晰的文档,我建议您查看spray website
  • @Thelonius 有用吗?我同意这方面的 Spray 文档(仍然)不足。

标签: scala akka akka-http


【解决方案1】:

感谢来自@Java Anto 的指针,对我有用的解决方案如下。

trait AuthorizationDirectives extends JWTService {

      val JWT_COOKIE_NAME = "jwt_cookie_name"
      val JWT_HEADER_NAME = "jwt_cookie_header"
      val INVALID_TOKEN_MESSAGE = "The provided token is not valid."
      val MISSING_ROLE_MESSAGE = "User does not have the required role."

  def authorizeRole(role: String): Directive0 = {
    extractToken.flatMap(validateToken)
      .flatMap(extractRoles)
      .flatMap(validateRole(role)(_))
  }

  def extractRoles(token: String): Directive1[String] = {
    // decode the token
    val decoded = decodeJWT(token)
    // check if decode was successful
    decoded match {
      case Some(_) => {
        // get the role string
        val rolesString = extractRoleCodesFromJWT(decoded.get)
        rolesString match {
          // return rolestring if present
          case Some(_) => provide(rolesString.get)
          case None => reject(AuthorizationFailedRejection)
        }
      }
      case None => reject(AuthorizationFailedRejection)
    }
  }

  def validateRole(role: String)(roles: String): Directive0 = {
    if (roles.contains(role))
      pass
    else
      reject(AuthorizationFailedRejection)
  }

  def validateToken(token: Option[String]): Directive1[String] = {
    token match {
      case Some(_) => validate(validateJWT(token.get), INVALID_TOKEN_MESSAGE) & provide(token.get)
      case None => reject(AuthorizationFailedRejection)
    }
  }

  def extractToken: Directive1[Option[String]] = {
    extractCookie.flatMap {
      case None => extractHeader
      case t@Some(_) => provide(t)
    }
  }

  def extractCookie: Directive1[Option[String]] = optionalCookie(JWT_COOKIE_NAME).map(_.map(_.value))

  def extractHeader: Directive1[Option[String]] = optionalHeaderValueByName(JWT_HEADER_NAME)

}

然后可以像这样使用该指令:

trait CRUDServiceRoute[ENTITY, UPDATE] extends BaseServiceRoute with Protocols with AuthorizationDirectives with CorsSupport {

import StatusCodes._

val requiredRoleForEdit = USER
val crudRoute: Route =
    pathEndOrSingleSlash {
      corsHandler {
        get {
          complete(getAll().map(entityToJson))
        } ~
          put {
            authorizeRole(requiredRoleForEdit) {
              entity(entityUnmarshaller) { t =>
                complete(Created -> create(t).map(idToJson))
              }
            }
          }
      }
    } ~
      pathPrefix(IntNumber) { id =>
        pathEndOrSingleSlash {
          corsHandler {
            get {
              complete(getById(id).map(entityToJson))
            }
          } ~
            corsHandler {
              put {
                authorizeRole(requiredRoleForEdit) {
                  entity(updateUnmarshaller) { tupd =>
                    complete(update(id, tupd).map(entityToJson))
                  }
                }
              }
            } ~
            delete {
              corsHandler {
                authorizeRole(requiredRoleForEdit) {
                  onSuccess(remove(id)) { ignored =>
                    complete(NoContent)
                  }
                }
              }
            }
        }
      }

  def entityToJson(value: Seq[ENTITY]): JsValue

  def entityToJson(value: Option[ENTITY]): JsValue

  def idToJson(value: Option[Long]): JsValue

  def entityUnmarshaller: FromRequestUnmarshaller[ENTITY]

  def updateUnmarshaller: FromRequestUnmarshaller[UPDATE]

  // CRUD service methods. These are implemented by the services used in the concrete routes
  def getAll(): Future[Seq[ENTITY]]

  def getById(id: Long): Future[Option[ENTITY]]

  def create(entity: ENTITY): Future[Option[Long]]

  def update(id: Long, noteUpdate: UPDATE): Future[Option[ENTITY]]

  def remove(id: Long): Future[Int]
}

【讨论】:

  • 嘿,@Thelonius 这个reject 来自哪里?
  • 嗨@Harmeet Singh Taara,据我所知,它是Spray 的内置指令。不幸的是,我无法再访问代码,所以我无法验证 100%
  • 谢谢@Thelonius的回复,我发现其实可以从akka.http.scaladsl.server.directives.RouteDirectivesimport.
猜你喜欢
  • 1970-01-01
  • 2021-10-29
  • 1970-01-01
  • 2013-11-16
  • 1970-01-01
  • 1970-01-01
  • 2013-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多