Our solution applies to JBoss portal, too. As it is stated before, with a small effort, Acegi Security Framework can be easily integrated with any JSR-168 compatible portal. Let’s walk over the steps that are needed for JBoss Portal 2.6.1.
JBoss Portal uses web container security to perform its authentication. It uses form based authentication. You can configure LDAP as authentication backend and as user realm. Acegi Security Framework provides container adapters to delegate its authentication to web containers. However, this approach has several drawbacks, including necessity for copying acegi-security.jar and several of its dependents into common library folders in container.
In our solution we let JBoss perform its own authentication using web container security. It will use LDAP as authentication backend. After a user authenticated, JBoss places a valid Subject entity into JNDI with name “env/security/subject”.
We will look up JNDI for the Subject, create a valid Acegi Authentication using that Subject entity, and place that Authentication token into SecurityContextHolder.
Steps to configure LDAP are as follows.
Please be careful that, LDAP entry which corresponds to admin role must have cn=”Admin” value. First letter “A” must be upper case here.
This part is actually optional. It is not necessary to propagate SecurityContext to portlet web applications. You may need this part when you need to configure Acegi in portal web application of JBoss.
In order to run Acegi Security Framework in portal-server web application, we first need to define Spring’s ContextLoaderListener to create application context during portal startup. Just add following part into WEB-INF/web.xml file.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/appcontext/spring-beans.acegi-portlet-jboss.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
After that configuration, we need to add filter definition into web.xml. And add its filter mapping as the first mapping in the web.xml file.
<filter>
<filter-name>AcegiSecurityFilter</filter-name>
<filter-class>
org.acegisecurity.util.FilterToBeanProxy
</filter-class>
<init-param>
<param-name>targetBean</param-name>
<param-value>filterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AcegiSecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
FilterToBeanProxy just delegates calls to targetBean defined in ApplicationContext. By that way we are able to define Filter instances as Spring managed beans.
Let’s look at spring-beans.acegi-portlet-jboss.xml file. I have developed org.acegisecurity.portlet.jboss.auth.JbossIntegrationFilter to query JNDI for a valid Subject entity, and use it to form a valid Acegi Authentication token. By that way, Acegi doesn’t involve in authentication process at first place, and only waits for a valid Subject to be placed into JNDI tree.
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=jbossIntegrationFilter,anonymousProcessingFilter
</value>
</property>
</bean>
<bean id="jbossIntegrationFilter"
class="org.acegisecurity.portlet.jboss.auth.JbossIntegrationFilter">
<property name="securityContextPopulator">
<ref local=" securityContextPopulator"/>
</property>
</bean>
<bean id="securityContextPopulator" class="org.acegisecurity.portlet.jboss.context.SecurityContextPopulator">
<property name="subjectToAuthenticationTransformer">
<ref local="subjectToAuthenticationTransformer"/>
</property>
</bean>
<bean id="subjectToAuthenticationTransformer" class="org.acegisecurity.portlet.jboss.context.SubjectToAuthenticationTransformer">
</bean>
As you see there are only two filters configured in filterChainProxy. First is jbossIntegrationFilter, and the second is anonymousProcessingFilter. jbossIntegrationFilter uses securityContextPopulator and subjectToAuthenticationTransformer beans to perform its job. If there isn’t any valid Authentication token placed into SecurityContextHolder, anonymousProcessingFilter puts an AnonymousAuthentication token into it.
Add following jars into WEB-INF/lib folder.
acegi-portlet.jar
acegi-security.jar
cglib-nodep.jar
commons-codec.jar
commons-digester.jar
commons-discovery.jar
commons-lang.jar
commons-logging.jar
jakarta-oro.jar
log4j.jar
spring.jar
Finally, don’t forget to add those JVM parameters -Dtarget.platform=dev -Duser.language=en -Duser.country=US appropriate point in bin\run.bat file.
JBoss portal uses
PortletContainerImpl class to wrap any deployed portlet instance and invokes it
later using that PortletContainer instance. If we are able to intercept just
before this portlet invocation, obtain a valid Authentication using JNDI
registered Subject, and then populate SecurityContextHolder with that Authentication
object.
The important point here
is class loading issues. We dont want to copy acegi-security and any of its
related jars into shared folders of JBoss. Each portlet web application, and
portal application (portal-server.war) itself should posses their copies of
those jars. By that way, we will have complete isolation between jars versions
of those applications. However, when we keep jars at web application
classloader level, then our aspect which intercepts
PortletContainerImpl.dispatch(..), which is contained by portal-portlet-lib.jar
located in \server\default\deploy\jboss-portal.sar\lib folder, will have
problem at accessing those classes such as Acegi’s Authentication, and
SecurityContextHolder classes in portal web application’s WEB-INF/lib folder.
We need to load them, using web application’s classloader, and execute
necessary logic to propagate SecurityContext into portlet application.
PortletInvocationResponse around(PortletInvocation invocation) :
execution(public PortletInvocationResponse
org.jboss.portal.portlet.impl.jsr168.PortletContainerImpl.dispatch(..))
&& args(invocation) {
PortletContainer
portletContainer = (PortletContainer)thisJoinPoint.getTarget();
try {
propagateSecurityContextToPortlet(
portletContainer.getApplication());
} catch(Exception ex) {
//just ignore to let execution of target continue...
}
PortletInvocationResponse
response = proceed(invocation);
try {
clearSecurityContextToPortlet(
portletContainer.getApplication());
} catch(Exception ex) {
//just ignore...
}
return response;
}
As you see from above code piece, we have an around advice for PortletContainerImpl.dispatch(..) method. Before letting target proceed, we propagate SecurityContext from portal to portlet web application’s SecurityContextHolder. When the execution of method completes, we need to clear SecurityContextHolder of that portlet web application.
You need to replaced weaved portal-portlet-lib.jar with old one in server\default\deploy\jboss-portal.sar\lib folder, and put aspectjrt.jar into that folder as well.
Caution: If it is suitable for you to place related jars into common library folder (server\default\deploy\jboss-portal.sar\lib) of JBoss Portal, then you don’t need this aspect. All you need to enable Acegi Security in your portal-server.war web application as explained above. If you choose this approach you must remove acegi-security.jar from your portlet web applications own WEB-INF/lib folder, and JBoss portal web application’s WEB-INF/lib folder. Otherwise, SecurityContext couldn’t be transferred from one SecurityContextHolder (loaded by web app classloader of portal-server.war) to another one (loaded by a portlet web app classloader). There must be only one copy of acegi-security.jar in shared lib folder.