Scalatra响应HMAC计算

时间:2014-10-17 11:05:49

标签: scala servlets hmac scalatra

我正在使用Scalatra开发Web服务,我想使用HMAC进行双向身份验证。

到目前为止,我已经对服务器实施了客户端身份验证:客户端(Android应用程序)使用以下参数为每个请求计算HMAC / SHA512:共享密钥,HTTP方法,URL,一些标头(timestamp,clientId等)和请求正文(如果它是POST或PUT)。然后将此HMAC添加到特定标头,并将请求发送到服务器(使用HMAC验证请求标头中的HMAC,计算与客户端相同)。

现在,我想做相反的事情:让服务器使用存储的共享密钥,请求HTTP方法,URL和响应正文对客户端进行身份验证。

到目前为止,我发现我可以覆盖renderResponse(actionResult: Any)renderResponseBody(actionResult: Any)甚至renderPipeline而且我已经决定采用重写renderPipeline因为它似乎是最容易处理的。

在我的覆盖renderPipeline中,我将响应主体转换为字节数组(如果提供File,则将内容中的服务File加载),计算HMAC并将其添加到{ {1}}标题。

我想知道的是:是否存在覆盖response这种方式会破坏上述身份验证功能的情况(例如renderPipeline未被调用或被多次调用或标题已被删除在Scalatra中调用befor renderPipeline来渲染正文)或其他一些功能?

作为注释,当动作返回renderPipeline并且动作直接写入响应输出时,我不计算HMAC。

1 个答案:

答案 0 :(得分:1)

我有完全相同的问题需要解决。我使用了一个延伸Handler的特征,就像它在GZipSupport.scala中所做的那样,并使用此answer作为参考实现。

我构建了一个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
}

然后是ResponseCopierHttpServletResponseWrapper,其中包含之前定义的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方法通过使用回调ScalatraBase.onRenderedComplete完成Scalatra操作后添加标头。

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*/
  }

}