Reputation: 3669
I'm developing a web service using Scalatra and I want to use HMAC for bidirectional authentication.
So far, I've implemented the client authentication to the server: the client (an Android app) calculates a HMAC/SHA512 for each request using these parameters: a shared secret, the HTTP method, URL, some headers (timestamp, clientId etc) and the request body (if it's a POST or a PUT). This HMAC is then added to a specific header and the request is sent to the server (which validates the HMAC in the request header with a HMAC that it calculates the same as the client).
Now, I want to do the opposite thing: have the server authenticate to the client using the stored shared secret, the request HTTP method, URL and the response body.
So far, I've found that I can override renderResponse(actionResult: Any)
, renderResponseBody(actionResult: Any)
or even renderPipeline
and I've decided to go with overriding renderPipeline
as it seems to be the easiest to handle.
In my overriden renderPipeline
I transform the response body to a byte array (loading the served File
in memory if serving a File
), calculate the HMAC and add it to the response
headers.
What I want to know is: are there cases when overriding renderPipeline
this way would break either the authentication functionality presented above (like renderPipeline
not being called or being called multiple times or the headers having been sent befor renderPipeline
is called to render the body) or some other functionality in Scalatra?
As a note I do not calculate the HMAC when the action returns Unit
and the response output is being written directly by the action.
Upvotes: 1
Views: 224
Reputation: 353
I had exactly the same problem to solve. I used a trait extending Handler
, just like it's done in the GZipSupport.scala and using this answer as a reference implementation.
I built a ServletOutputStreamCopier
which holds a copy of the original OutputStream
and every byte to both streams:
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
}
Then a ResponseCopier
, which is an HttpServletResponseWrapper
with the previously defined ServletOutputStreamCopier
and exposes the copy
to the outside:
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
}
Finally the handle
method takes care of adding the header after the Scalatra action was completed by using the callback 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*/
}
}
Upvotes: 1