我正在使用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。
答案 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
}
然后是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
方法通过使用回调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*/
}
}