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!