喷涂身份验证不会返回正确的HTTP标头

时间:2014-07-02 01:44:13

标签: spray

这是我的路线定义:

lazy val route =
      pathPrefix("sec") {
        pathEnd {
          complete("ended!")
        } ~
        //weird enough, if authen fails, it sends 404 instead of authen fails
      authenticate(basicUserAuthenticator) { AuthInfo =>
        pathPrefix("casper" / Segment) { token =>
            post {
                entity(as[JObject]) { company => ctx =>
                  if (token == "123") {
                    complete("this is good comm!")
                  }
                  val response = (secCompanyActor ? jObjectFromCasper(company))
                    .mapTo[CasperOk]
                    .map(result => result)
                    .recover{case _ => "error!"}
                  complete(response)
                }
            } ~
            put {
              complete {
                "successful"
              }
            }
          }
      }

我只有两个问题(几乎困扰了我两天):1。我实施了基本身份验证,但是,它并没有正确拒绝。它的作用是在认证失败时返回404,或者如果认证通过则返回200。

同样,当我添加此authenticate功能时,我的entity(as[JObject])停止工作。我收到这个错误:

➜  /  http -a username:123 POST http://127.0.0.1:8080/sec/casper/12 hello="this" this="that"
HTTP/1.1 406 Not Acceptable
Content-Length: 104
Content-Type: text/plain; charset=UTF-8
Date: Wed, 02 Jul 2014 01:38:15 GMT
Server: some server

Resource representation is only available with these Content-Types:
text/plain; charset=UTF-8
text/plain

这太令人讨厌了!

以下是我在身份验证上的实现:

  def basicUserAuthenticator(implicit ec: ExecutionContext): AuthMagnet[AuthInfo] = {
    def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
      val user = userPass.map[Option[User.User]](u => getUser(u.user, u.pass))
      user.get match {
        case Some(u) => Some(AuthInfo(u.name, rejectedOrNot = true))
        case None => Some(AuthInfo(None, rejectedOrNot = false))
      }
    }

    def authenticator(userPass: Option[UserPass]): Future[Option[AuthInfo]] = Future { validateUser(userPass) }

    BasicAuth(authenticator _, realm = "Lab Private API")
  }

我几乎要放弃并使用uri查询作为身份验证,这有点俗气,但它比不起作用的东西更好!

2 个答案:

答案 0 :(得分:1)

首先,关于POST entityctx =>是您的问题。看看http://spray.io/documentation/1.2.1/spray-routing/advanced-topics/understanding-dsl-structure/。简而言之,以下是等同的:

val route: Route = complete("yeah")
val route: Route = _.complete("yeah")
val route: Route = { ctx => ctx.complete("yeah") }

因此,如果您需要RequestContext用于路线中的任何其他内容,则需要在complete对象上调用RequestContext

关于身份验证,您的身份验证器的问题是您始终返回Some,Spray将其解释为"身份验证成功"。要使身份验证器正常工作,如果身份验证失败,则需要返回None。像这样:

def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
  for {
    up <- userPass
    u <- User.getUser(up.user, up.pass)
  } yield AuthInfo(u.name)

  // Desugars to userPass.flatMap(u => User.getUser(u.user, u.pass)).map(u => AuthInfo(u.name))
}

请注意,使用flatMap代替map来获取用户(它会自动将结果Option[Option[User]]展开到Option[User]),如果有任何失败,您只需返回一个None向Spray发出信号,表明身份验证没有成功。

答案 1 :(得分:0)

我终于找到了一个令人不满意的解决方案。

首先,无法从此自引用中提取POST entityctx =>

我把它当作:

entity(as[JObject]) { company => ctx: RequestContext =>
...
}

但不是。我不能。由于某种原因,这不起作用。它会阻止JSON对象的接收。

正确响应身份验证的另一个问题。我提出的解决方案是:

entity(as[JObject]) { company => ctx: RequestContext =>
  AuthInfo.rejectedOrNot match {
      case true => complete("good job!")
      case false => complete(Unauthorized, "it's unauthorized")
  }
}

Unauthorized是从import spray.http.StatusCodes._

导入的

我很惊讶我必须手动发送拒绝,而发送失败的方式不是通过reject(),而是通过complete()。也许我错了,但我找不到更好的方法。