Thursday, September 15, 2011

Using Spring Security with Ring, Jetty, and Compojure

All the source code for this can be found at https://github.com/lispnik/ring-spring-security

Spring Security (formerly Acegi Security) is a great framework for adding authentication and authorization to your Java web app. I wanted to take advantage of it in my Compojure apps. Here's an example of how to do it:



The next step is to wrap this in a ring/ring-adapter-jetty configurator (i.e., a function that takes a server):



To illustrate the integration, here is an example Spring Security configuration which establishes two users and two roles (user and administration) and applies it to URL patterns. If the user issues a HTTP request with a URL pattern that requires authorization and they are not already authenticated, then Spring Security as configured below will actually render a plain-looking login form before proceeding. We get this basic functionality for free with Spring Security although it can be replaced with a custom form of our own design.



The Compojure application this secures looks like this:



Here the HTML rendering prints out the Spring context and security context which contain lots of goodies we can use later:


2 comments:

  1. Your example works, but this occasionally pops up in the logs:
    Exception: java.lang.IllegalArgumentException: ServletContext must not be null
    Assert.java:112 org.springframework.util.Assert.notNull
    WebApplicationContextUtils.java:109 org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext
    WebApplicationContextUtils.java:99 org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext
    core.clj:31 ring-spring-security.core/application-context
    core.clj:50 ring-spring-security.core/wrap-reload-spring[fn]
    core.clj:57 ring-spring-security.core/wrap-dump[fn]
    reload.clj:14 ring.middleware.reload/wrap-reload[fn]
    stacktrace.clj:15 ring.middleware.stacktrace/wrap-stacktrace-log[fn]
    stacktrace.clj:79 ring.middleware.stacktrace/wrap-stacktrace-web[fn]
    Var.java:365 clojure.lang.Var.invoke
    jetty.clj:16 ring.adapter.jetty/proxy-handler[fn]
    (Unknown Source) ring.adapter.jetty.proxy$org.mortbay.jetty.handler.AbstractHandler$0.handle
    HandlerCollection.java:114 org.mortbay.jetty.handler.HandlerCollection.handle
    HandlerWrapper.java:152 org.mortbay.jetty.handler.HandlerWrapper.handle
    Server.java:322 org.mortbay.jetty.Server.handle
    HttpConnection.java:542 org.mortbay.jetty.HttpConnection.handleRequest
    HttpConnection.java:928 org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete
    HttpParser.java:549 org.mortbay.jetty.HttpParser.parseNext
    HttpParser.java:212 org.mortbay.jetty.HttpParser.parseAvailable
    HttpConnection.java:404 org.mortbay.jetty.HttpConnection.handle
    SocketConnector.java:228 org.mortbay.jetty.bio.SocketConnector$Connection.run
    QueuedThreadPool.java:582 org.mortbay.thread.QueuedThreadPool$PoolThread.run

    Any idea why?

    ReplyDelete
  2. Re: Exception: java.lang.IllegalArgumentException: ServletContext must not be null

    It seems to be because I'm reloading the Spring application context on every request. The exception seems harmless but you can work around it by not reloading the Spring application context on every request. e.g. In the following (from core.clj), comment out (wrap-reload-spring):

    (def app
    (-> (handler/site main-routes)
    (wrap-reload-spring)
    (wrap-dump)
    (wrap-reload '(ring-spring-security.core))
    (wrap-stacktrace)))

    ReplyDelete