Отклик Scalatra Расчет HMAC

Я разрабатываю веб-службу с помощью Scalatra и хочу использовать HMAC для двунаправленной аутентификации.

До сих пор я реализовал аутентификацию клиента на сервере: клиент (приложение для Android) вычисляет HMAC/SHA512 для каждого запроса, используя следующие параметры: общий секрет, метод HTTP, URL-адрес, некоторые заголовки (метка времени, clientId и т. д.). ) и тело запроса (если это POST или PUT). Затем этот HMAC добавляется к определенному заголовку, и запрос отправляется на сервер (который проверяет HMAC в заголовке запроса с помощью HMAC, который он вычисляет так же, как и клиент).

Теперь я хочу сделать обратное: заставить сервер аутентифицировать клиента, используя сохраненный общий секрет, HTTP-метод запроса, URL-адрес и ответ body.

До сих пор я обнаружил, что могу переопределить renderResponse(actionResult: Any), renderResponseBody(actionResult: Any) или даже renderPipeline, и я решил переопределить renderPipeline, так как с ним проще всего справиться.

В моем переопределении renderPipeline я преобразовываю тело ответа в массив байтов (загружая обслуживаемый File в память, если обслуживаю File), вычисляю HMAC и добавляю его в заголовки response.

Что я хочу знать, так это: есть ли случаи, когда переопределение renderPipeline таким образом нарушит либо функциональность аутентификации, представленную выше (например, renderPipeline не вызывается или вызывается несколько раз, либо заголовки, отправленные до renderPipeline, вызываются для рендеринга тела) или какая-то другая функциональность в Scalatra?

В качестве примечания я не вычисляю HMAC, когда действие возвращает Unit, а вывод ответа записывается непосредственно действием.


person lucian.pantelimon    schedule 17.10.2014    source источник


Ответы (1)


У меня была точно такая же проблема, чтобы решить. Я использовал трейт, расширяющий Handler, точно так же, как это сделано в GZipSupport.scala и использовать этот ответ в качестве эталонной реализации.

Я построил ServletOutputStreamCopier, который содержит копию исходного OutputStream и каждый байт для обоих потоков:

class ServletOutputStreamCopier(orig: ServletOutputStream) extends ServletOutputStream {
    val copy: ByteArrayOutputStream = new ByteArrayOutputStream(1024)

    override def write(b: Int): Unit = {
      orig.write(b)
      copy.write(b)
    }
    override def setWriteListener(writeListener: WriteListener): Unit = orig.setWriteListener(writeListener)

    override def isReady: Boolean = orig.isReady

    def getCopy: Array[Byte] = copy.toByteArray
}

Затем ResponseCopier, который является HttpServletResponseWrapper с ранее определенным ServletOutputStreamCopier и выставляет copy наружу:

class ResponseCopier(res: HttpServletResponse, sos:   ServletOutputStreamCopier, w: PrintWriter) extends HttpServletResponseWrapper(res) {
    override def getOutputStream: ServletOutputStream = new ServletOutputStreamCopier(sos)

    override def getWriter: PrintWriter = w

    override def setContentLength(i: Int) = {}

    def getCopy: Array[Byte] = sos.getCopy
}

Наконец, метод handle обеспечивает добавление заголовка после завершения действия Scalatra с помощью обратного вызова ScalatraBase.onRenderedComplete.

trait SignedResponseSupport extends Handler {
  self: ScalatraBase =>

  abstract override def handle(req: HttpServletRequest, res: HttpServletResponse): Unit = {
    withRequestResponse(req, res) {
      val sosc = new ServletOutputStreamCopier(res.getOutputStream)
      val w = new PrintWriter(sosc)
      val wrapped = new ResponseCopier(response,sosc ,w)

      ScalatraBase.onRenderedCompleted { _ =>
        w.flush()
        w.close()
        val password = "secret-password"
        val signature = signResponseBody(wrapped.getCopy, password)
        wrapped.addHeader("X-Response-Signature", signature)
        }
      }
      super.handle(req, wrapped)
    }

  def signResponseBody(body: Array[Byte], password: String): String = {
    /*signing goes here*/
  }

}
person zucaritask    schedule 11.04.2015