« Starting and configuring a Jetty 7 instance in Equinox | Main | Deploying a servlet in Jetty 7 using OSGi-DS »
Friday
Jan282011

Adding authentication to a Jetty 7 deployment through jetty-web.xml

In a setup similar to the one I wrote about here, we had to add some basic http authentication. To add authentication to a context handler all that is needed is to set a security handler to it. This can be accomplished in at least two ways, programmatically or using the web.xml. The former is a bit ugly and the latter stops us from exposing our servlets using OSGi-DS (see previous post). After some internal discussion we opted for a third variant; using jetty-web.xml. I mean, since we had to put it there, so we might as well put it to good use!

From the start we decided we would want to share the authentication configuration between (most of) the different context handlers we have now exposed as OSGi-services. This means we needed some way of sharing the data that we create. We have the realm file used by the HashLoginService configurable as a system property, and we could share that, creating new HashLoginService instances in each context handler. However, it gets more complicated if we want to configure some other LoginService and use that everywhere. So, we decided to attempt to share the actual LoginService instance. In fact, after some experimentation and refactoring of the xml-files I ended up with being able to share the entire SecurityHandler between the services using Server#setAttribute().

This resulted in the following addition to our jetty.xml:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN"
    "http://www.eclipse.org/jetty/configure.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">

    <!-- old stuff here -->

    <New class="org.eclipse.jetty.security.HashLoginService"
        id="DefaultLoginService">
        <Set name="name">MyRealm</Set>
        <Set name="config">
            <SystemProperty name="jetty.authrealm"
                default="/etc/webthing/realm.properties" />
        </Set>
        <Set name="refreshInterval">5</Set>
        <Call name="start"/>
    </New>

    <New class="org.eclipse.jetty.http.security.Constraint"
        id="DefaultSecurityConstraint">
        <Set name="authenticate">true</Set>
        <Set name="name">BASIC</Set>
        <Set name="roles">
            <Array type="java.lang.String">
                <Item>admin</Item>
            </Array>
        </Set>
    </New>

    <New class="org.eclipse.jetty.security.ConstraintSecurityHandler"
        id="DefaultSecurityHandler">
        <Set name="loginService">
            <Ref id="DefaultLoginService" />
        </Set>
        <Set name="realmName">MyRealm</Set>
        <Call name="addConstraintMapping">
            <Arg>
                <New class="org.eclipse.jetty.security.ConstraintMapping">
                    <Set name="pathSpec">/*</Set>
                    <Set name="constraint">
                        <Ref id="DefaultSecurityConstraint" />
                    </Set>
                </New>
            </Arg>
        </Call>
    </New>

    <Call name="addBean">
        <Arg><Ref id="DefaultLoginService" /></Arg>
    </Call>

    <Call name="setAttribute">
        <Arg>DefaultSecurityConstraint</Arg>
        <Arg><Ref id="DefaultSecurityConstraint" /></Arg>
    </Call>

    <Call name="setAttribute">
        <Arg>DefaultLoginService</Arg>
        <Arg><Ref id="DefaultLoginService" /></Arg>
    </Call>

    <Call name="setAttribute">
        <Arg>DefaultSecurityHandler</Arg>
        <Arg><Ref id="DefaultSecurityHandler" /></Arg>
    </Call>

</Configure>

Notice how we set attributes on the server for several of the objects we created. We can't/don't want to reuse the same SecurityHandler everywhere (actually not, read to the end...), but we still want to reuse some of the other parts. This allows us to change some of the settings, but still lets us reconfigure the LoginService from a single place.

Next, I just had to swap the otherwise empty jetty-web.xml for this this:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN"
    "http://www.eclipse.org/jetty/configure.dtd">

<Configure class="test.webthing.WebThing">
    <Property name="Server">
        <Call name="getAttribute" id="DefaultSecurityHandler">
            <Arg>DefaultSecurityHandler</Arg>
        </Call>
    </Property>
    <Set name="securityHandler">
        <Ref id="DefaultSecurityHandler" />
    </Set>
</Configure>

Yea, right. Finally. However, once a second component is added, this thing reared its head:

2011-01-31 12:51:11,173 INFO  org.eclipse.jetty.util.log Deployable added: webthing-1.0.0.qualifier/jetty-web.xml
2011-01-31 12:51:11,206 WARN  org.eclipse.jetty.util.log FAILED t.w.WebThing{/hello,null}: java.lang.IllegalStateException: STARTED
2011-01-31 12:51:11,206 WARN  org.eclipse.jetty.util.log Unable to reach node goal: started
java.lang.IllegalStateException: STARTED
    at org.eclipse.jetty.server.handler.HandlerWrapper.setHandler(HandlerWrapper.java:70)
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:205)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:584)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:55)
    at org.eclipse.jetty.deploy.bindings.StandardStarter.processBinding(StandardStarter.java:36)
    at org.eclipse.jetty.deploy.AppLifeCycle.runBindings(AppLifeCycle.java:180)
    at org.eclipse.jetty.deploy.DeploymentManager.requestAppGoal(DeploymentManager.java:481)
    at org.eclipse.jetty.deploy.DeploymentManager.addApp(DeploymentManager.java:137)
    at org.eclipse.jetty.osgi.boot.OSGiAppProvider.addContext(OSGiAppProvider.java:197)
    at org.eclipse.jetty.osgi.boot.OSGiAppProvider.addContext(OSGiAppProvider.java:179)
    at org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper.registerContext(WebBundleDeployerHelper.java:400)
    at org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper.registerContext(WebBundleDeployerHelper.java:348)
    at org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper.registerContext(WebBundleDeployerHelper.java:308)
    at org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker.serviceChanged(JettyContextHandlerServiceTracker.java:247)
    at org.eclipse.osgi.internal.serviceregistry.FilteredServiceListener.serviceChanged(FilteredServiceListener.java:104)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.dispatchEvent(BundleContextImpl.java:933)
    at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:227)
    at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:149)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEventPrivileged(ServiceRegistry.java:756)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEvent(ServiceRegistry.java:711)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.register(ServiceRegistrationImpl.java:130)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.registerService(ServiceRegistry.java:206)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.registerService(BundleContextImpl.java:507)
    at org.eclipse.equinox.internal.ds.InstanceProcess.registerService(InstanceProcess.java:504)
    at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponents(InstanceProcess.java:212)
    at org.eclipse.equinox.internal.ds.Resolver.buildNewlySatisfied(Resolver.java:441)
    at org.eclipse.equinox.internal.ds.Resolver.enableComponents(Resolver.java:213)
    at org.eclipse.equinox.internal.ds.SCRManager.performWork(SCRManager.java:800)
    at org.eclipse.equinox.internal.ds.SCRManager$QueuedJob.dispatch(SCRManager.java:767)
    at org.eclipse.equinox.internal.ds.WorkThread.run(WorkThread.java:89)
    at org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor.run(Executor.java:70)

Turns out it's not a particularly good idea to share the security handler. So, in the end the configuration for the components looked like this:

<?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN"
    "http://www.eclipse.org/jetty/configure.dtd">

<Configure class="test.webthing.WebThing">
    <Property name="Server">
        <Call name="getAttribute" id="DefaultLoginService">
            <Arg>DefaultLoginService</Arg>
        </Call>
        <Call name="getAttribute" id="DefaultSecurityConstraint">
            <Arg>DefaultSecurityConstraint</Arg>
        </Call>
    </Property>

    <Set name="securityHandler">
        <New class="org.eclipse.jetty.security.ConstraintSecurityHandler">
            <Set name="loginService">
                <Ref id="DefaultLoginService" />
            </Set>
            <Set name="realmName">MyRealm</Set>
            <Call name="addConstraintMapping">
                <Arg>
                    <New class="org.eclipse.jetty.security.ConstraintMapping">
                        <Set name="pathSpec">/*</Set>
                        <Set name="constraint">
                            <Ref id="DefaultSecurityConstraint" />
                        </Set>
                    </New>
                </Arg>
            </Call>
        </New>
    </Set>
</Configure>

Now, if there were only some nice way of injecting these services into our OSGi-components without going though the Server attributes...

Improvement suggestions and corrections welcome!

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.
  • Response
    Response: coc cheat
    Fnordology - Journal - Adding authentication to a Jetty 7 deployment through jetty-web.xml

Reader Comments (1)

Hi!

I'm trying to add Basic authentication to a SolR application. The changes I've made to etc/webdefault.xml already have taken effect (since I'm getting a java.lang.illegalstateexception: No LoginService for org.eclipse.jetty.security.authentication), but now adding your code to etc/jetty.xml throws java.lang.ClassNotFoundException: org.eclipse.jetty.http.security.Constraint. Can you help me getting this to work?

December 5, 2013 | Unregistered CommenterDeever

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>