在Scala Play框架中,有没有简单的方法来验证表单请求上的签名

yhxst69z  于 2022-11-09  发布在  Scala
关注(0)|答案(1)|浏览(198)

我正在尝试编写一个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")) }
    }

  }

}
tp5buhyn

tp5buhyn1#

您是对的,**在Play!**项目上验证HMAC签名并不是一种简单的方法。最后,您的方法似乎有一个非常合理的实现,并且可以更容易地适应使用HMAC签名的其他提供商,如GitHub和Strike。
我真的认为为Action提供一个 Package 了RequestAction,或者甚至为一个Service提供一个为其他提供者进行定制签名验证的方法,这可能是一个很好的开源项目。为什么不在GitHub上帮助Play社区进行一个开源项目呢?

相关问题