我正在尝试编写一个Scala Play框架动作,它将在包含表单URL编码数据的传入POST请求上验证HmacSHA256签名。
这在Play框架中似乎并不简单,因为:i)Actions构建器只能访问头,但不能访问请求正文,ii)为了计算签名,我们必须将请求正文视为Array[ByteString]
,但当我们处理表单数据时,我们必须将其视为Map[String, Seq[String]]
,问题是Play迫使我们为我们的请求选择一种类型,而我们不能轻松地将请求正文“转换”为不同的类型。
我能想到的唯一解决方案是使用ActionRefiner
,它返回嵌入回调的WrappedRequest
来验证签名。回调使用FormUrlEncodedParser.parse(new String(request.body.toArray))
重新解析数据。此方法如下面的代码所示。
这一切似乎过于错综复杂。有没有一种更简单的方法来验证HMAC签名,或者我只是遇到了API的限制?
package actions
import akka.util.ByteString
import com.google.inject.Inject
import play.api.Logging
import play.api.mvc.Results.Unauthorized
import play.api.mvc._
import play.core.parsers.FormUrlEncodedParser
import services.SlackSignatureVerifierService
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
class SlackRequest[A](
val validateSignature: String => Try[String],
request: Request[A]
) extends WrappedRequest[A](request)
object SlackSignatureVerifyAction {
implicit class SlackRequestByteStringValidator(
slackRequest: SlackRequest[ByteString]
) {
def validateSignatureAgainstBody(): Try[Map[String, Seq[String]]] = {
val raw = slackRequest.body.utf8String
slackRequest.validateSignature(raw) map { _ =>
FormUrlEncodedParser.parse(new String(slackRequest.body.toArray))
}
}
}
val HEADERS_TIMESTAMP: String = "X-Slack-Request-Timestamp"
val HEADERS_SIGNATURE: String = "X-Slack-Signature"
}
class SlackSignatureVerifyAction @Inject() (
val parser: BodyParsers.Default,
slackSignatureVerifierService: SlackSignatureVerifierService
)(implicit ec: ExecutionContext)
extends ActionBuilder[SlackRequest, AnyContent]
with ActionRefiner[Request, SlackRequest]
with Logging {
override protected def executionContext: ExecutionContext = ec
override protected def refine[A](
request: Request[A]
): Future[Either[Result, SlackRequest[A]]] = {
val timestamp =
request.headers.get(SlackSignatureVerifyAction.HEADERS_TIMESTAMP)
val signature =
request.headers.get(SlackSignatureVerifyAction.HEADERS_SIGNATURE)
(timestamp, signature) match {
case (Some(timestamp), Some(signature)) =>
Future.successful {
val validate = (body: String) =>
slackSignatureVerifierService.validate(timestamp, body, signature)
Right(new SlackRequest[A](validate, request))
}
case _ =>
Future { Left(Unauthorized("Invalid signature headers")) }
}
}
}
1条答案
按热度按时间tp5buhyn1#
您是对的,**在Play!**项目上验证HMAC签名并不是一种简单的方法。最后,您的方法似乎有一个非常合理的实现,并且可以更容易地适应使用HMAC签名的其他提供商,如GitHub和Strike。
我真的认为为
Action
提供一个 Package 了Request
的Action
,或者甚至为一个Service
提供一个为其他提供者进行定制签名验证的方法,这可能是一个很好的开源项目。为什么不在GitHub上帮助Play社区进行一个开源项目呢?