2014-10-17 30 views
1

我正在開發一個使用Scalatra的Web服務,我想使用HMAC進行雙向認證。到目前爲止,我已經實現了對服務器的客戶端身份驗證:客戶端(Android應用程序)使用以下參數爲每個請求計算HMAC/SHA512:共享密鑰,HTTP方法,URL,一些頭(時間戳,clientId等)和請求主體(如果它是POST或PUT)。這個HMAC然後被添加到一個特定的頭部,並且該請求被髮送到服務器(它使用與計算客戶端相同的HMAC來在請求頭部中驗證HMAC)。Scalatra響應HMAC調用

現在我想做相反的事情:讓服務器使用存儲的共享密鑰,請求HTTP方法,URL和響應body向客戶端進行身份驗證。

到目前爲止,我發現我可以重寫renderResponse(actionResult: Any)renderResponseBody(actionResult: Any)甚至renderPipeline,我已經決定去與壓倒一切的renderPipeline,因爲它似乎是最容易處理。

在我重寫renderPipeline我變換響應主體的字節數組(在內存中加載的服務File如果服File),計算HMAC,並把它添加到response頭。

我想知道的是:是否有案件覆蓋renderPipeline時,這種方式會破壞或者上面介紹的認證功能(如renderPipeline沒有被調用或者被多次調用或已befor renderPipeline發送的頭被調用,以呈現身體)還是Scalatra中的其他功能?

作爲一個說明,當動作返回Unit並且響應輸出由動作直接寫入時,我不計算HMAC。

回答

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 
} 

然後,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 
} 

最後Scalatra的行動是由完成後handle方法需要附加標題的護理使用回調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*/ 
    } 

}