Persisting Java Objs within Clojure
For the last day or so I've been wrestling with a problem using WebAuthN on a Clojure web app based on compojure and ring. There were a few helpful posts that got be in the right direction, and using Buddy helps, as it handles a lot of the route handling, but getting the actual WebAuthN handshake going was a bit of a pain.
The problem was that after the Registration step, you end up with a Java Object, an instance of com.webauthn4j.authenticator.AuthenticatorImpl and there is no simple way to serialize it out for storage in a database, so it was time to get creative.
I did a lot of digging, and I was able to find a nice way to deserialize the object, and return a JSON object, but there was no way to reconstitute it into an AuthenticatorImpl, so that had to be scrapped.
Then I found a reference to an Apache Commons lang object that supposedly was exactly what I wanted... it would serialize to a byte[], and then deserialize from that byte[] into the object. Sounds good... but I needed to save it in a Postgres database. Fair enough... let's Base64 encode it into a string, and then decode it on the way out.
The two key functions are very simple:
(:import org.apache.commons.lang3.SerializationUtils java.util.Base64) (def not-nil? (complement nil?)) (defn obj->b64s "This is a very useful function for odd Java Objects as it is an Apache tool to serialize the Object into a byte[], and then convert that into a Base64 string. This is going to be very helpful with the persistence of objects to the database, as for some of the WebAuthN objects, it's important to save them, as opposed to re-creating them each time." [o] (if (not-nil? o) (.encodeToString (Base64/getEncoder) (SerializationUtils/serialize o)))) (defn b64s->obj "This is a very useful function for odd Java Objects as it is an Apache tool to deserialize a byte[] into the original Object that was serialized with obj->b64s. This is going to be very helpful with the persistence of objects to the database, as for some of the WebAuthN objects, it's important to save them, as opposed to re-creating them each time." [s] (if (not-nil? s) (SerializationUtils/deserialize (.decode (Base64/getDecoder) s))))
These then fit into the saving and querying very simply, and it all works out just dandy. 🙂 I will admit, I was getting worried because I was about to regenerate the AuthenticatorImpl on each call, and that would have been a waste for sure.
The complete WebAuthN workflow is the point of another post, and a much longer one at that. But this really made all the difference.