这是我的路线定义:
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查询作为身份验证,这有点俗气,但它比不起作用的东西更好!
答案 0 :(得分:1)
首先,关于POST entity
,ctx =>
是您的问题。看看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 entity
:ctx =>
我把它当作:
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()
。也许我错了,但我找不到更好的方法。