SourceForge.net Logo

Integrating Liferay Portal with Acegi Security Framework. 1

Delegating Liferay Authentication to Acegi Security Framework. 1

Liferay auto.login.hooks. 7

Populating Acegi SecurityContext during Liferay Login Post Event 11

Propagating Acegi SecurityContext from Liferay Portal to Portlet Web Applications. 12

Intercepting Portlet Calls. 12

Retrieving SecurityContext on Portlet Side. 13

Retrieving SecurityContext When Ajax Calls Involved. 14

Quick Start with Liferay 4.3.x. 15

Using Login Post Event Mechanism.. 15

Using Auto Login Hook Mechanism.. 16

Quick Start with Liferay 4.2.x. 17

Using AutoLogin Hook Mechanism.. 17

Configuring Acegi-Portlet To Use LDAP. 17

 

Integrating Liferay Portal with Acegi Security Framework

 

Integrating Liferay with Acegi Security Framework is a two fold process. The first one is populating a valid authentication object in Acegi SecurityContextHolder, and the second one is propagating that authentication object from Liferay to individual portlet web applications which demand Acegi authorization features.

 

Populating a valid authentication object in SecurityContextHolder could be achieved with different ways. One way is to take authentication responsibility from Liferay and give it to Acegi. Actually, portlet containers are typical web applications; therefore there is no problem in employing Acegi Security Framework for authenticating and authorizing HTTP requests coming into portals. The important point here is to inform portal about currently authenticated user just after the login operation takes place. The other way is to let Liferay continue to authenticate its users, but create a valid Authentication object when Liferay post login event is received.

Delegating Liferay Authentication to Acegi Security Framework

 

Delegating authentication to Acegi Security Framework is giving Acegi the responsibility to authenticate current request. Acegi Security provides a comprehensive set of authentication methods, ranging from HTTP basic, and form based authentication to CAS integration.

We currently employ form based authentication with the choice of in memory or LDAP as user realms. You can, however, use any other authentication method supported by Acegi Security.

 

With this method we need to inform Liferay about currently authenticated user during login process. We currently have working solutions for two different versions of Liferay, which are 4.2.2 and 4.3.2. Letís see how to configure Acegi authentication and inform Liferay about authenticated user.

 

First of all you need to configure Acegi Security Framework to work with Liferay. You can start configuring Acegi with adding following part into ROOT/WEB-INF/web.xml file of your Liferay install.

 

<context-param>

††††† <param-name>contextConfigLocation</param-name>

††††† <param-value>

††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay.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 now walk over some important parts of Acegi bean configurations that make Authentication work.

 

Caution: I wonít cover every detail of Acegi; just provide enough information to ease understanding of how authentication works. You can refer Acegiís reference documentation or API for further details.

 

††††† <bean id="filterChainProxy"

††††††††††† class="org.acegisecurity.util.FilterChainProxy">

††††††††††† <property name="filterInvocationDefinitionSource">

††††††††††††††††† <value>

††††††††††††††††††††††† CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

††††††††††††††††††††††† PATTERN_TYPE_APACHE_ANT

††††††††††††††††† ††††† /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,anonymousProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterSecurityInterceptor

††††††††††††††††† </value>

††††††††††† </property>

††††† </bean>

 

 

As we stated earlier, Acegi is based on ServletFilter objects to perform its job. Requests matched by above filter-mapping, all requests for the above case, are delegated to filterChainProxy. filterChainProxy invokes each filter in sequence. Please note that, the order of those filters is very important for Acegi to function properly.

 

First filter is httpSessionContextIntegrationFilter, which populates SecurityContextHolder with any available SecurityContext object obtained from current HttpSession. It places SecurityContext into HttpSession at the end of each web request.

 

 

<bean id="logoutFilter"

††††††††††† class="org.acegisecurity.ui.logout.LogoutFilter">

 

<constructor-arg>

††††††††††† <value>/login.jsp</value>

††††† </constructor-arg>

 

††††† <constructor-arg>

††††††††††† <list>

††††††††††††††††† <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />

††††††††††† </list>

††††† </constructor-arg>

 

††††† <property name="filterProcessesUrl">

††††††††††† <value>/c/portal/logout</value>

††††† </property>

</bean>

 

logoutFilter is used to log a principal out. First constructor-arg defines where to go after logout. filterProcessesUrl identifies which URL must be processed to perform logout. We define it as /c/portal/logout as it is Liferay Portalís logout URL.

 

<bean id="authenticationProcessingFilter"

††††† ††††† class="com.liferay.portal.security.acegi.LiferayUserPopulatingAuthenticationProcessingFilter">

 

††††††††††† <property name="authenticationManager">

††††††††††††††††† <ref bean="authenticationManager" />

††††††††††† </property>

 

††††††††††† <property name="authenticationFailureUrl">

††††††††††††††††† <value>/login.jsp?error=true</value>

††††††††††† </property>

 

††††††††††† <property name="defaultTargetUrl">

††††††††††††††††† <value>/index.html</value>

††††††††††† </property>

 

</bean>

 

authenticationProcessingFilter is one of cornerstone filters in Acegi. It starts authentication process, and delegates actual work to its authenticationManager. We configure it with authenticationFailureUrl which tells where to go if authentication fails, and defaultTargetUrl which tells where to go if authentication succeeds unless there isnít any saved targetUrl found in current HttpSession. There is also another important parameter that needs to be mentioned here, although we use its default value in our configuration; filterProcessesUrl indicates in which URL request authentication processing starts. By default it has is /j_acegi_security_check.

 

Acegi is able to perform different types of authentications as stated earlier. For the sake of simplicity, we chose †form based authentication for this configuration. Acegi provides org.acegisecurity.ui.webapp.AuthenticationProcessingFilter class for form based authentication.

 

LiferayUserPopulatingAuthenticationProcessingFilter extends from AuthenticationProcessingFilter, and overrides its onSuccessfulAuthentication method. We populate user information with Liferay database if it doesnít already exist there.

 

††††† <bean id="exceptionTranslationFilter"

††††††††††† class="org.acegisecurity.ui.ExceptionTranslationFilter">

 

††††††††††† <property name="authenticationEntryPoint">

††††††††††††††††† <bean†††††† †††††††††††††††††††††††††††††††††††††††††††††††††††††††††† ††††† class="org.acegisecurity.ui.webapp.

AuthenticationProcessingFilterEntryPoint">

 

††††††††††† ††††† <property name="loginFormUrl">

††††††††††††††††† ††††† <value>/login.jsp</value>

††††††††††† ††††† </property>

††††† </bean>††††††††††

</property>

 

††††††††††† <property name="accessDeniedHandler">

††††††††††††††††† <bean

††††††††††† ††††† class="org.acegisecurity.ui.AccessDeniedHandlerImpl">

††††††††††††††††††††††† <property name="errorPage">

†††††††††††††††††††††††††††† <value>/accessDenied.jsp</value>

††††††††††††††††††††††† </property>

††††††††††††††††† </bean>

††††††††††† </property>

††††† </bean>

 

†††††

securityContextAwareRequestFilter wraps current HttpServletRequest object and return values for getRemoteUser() and isUserInRole() methods using its own SecurityContext owned Authentication object. Liferay depends on those HttpServletRequest methods to determine if current request needs authentication, and when a valid username is returned from getRemoteUser() method Liferay will decide that current request is already authenticated, and wonít try to authenticate it.

 

Caution: As there are some major differences on how a user is logged into Liferay in version 4.3.2 compared to version 4.2.2, you canít configure SecurityContextHolderAwareRequestFilter in Liferay 4.3.2 portal. Our solution for version 4.3.2 depends on configuration of auto.login.hook which is explained in the following section.

 

exceptionTranslationFilter handles any AccessDeniedException and AuthenticationException thrown in the filter chain. When an AccessDeniedException thrown, user is redirected to errorPage defined in accessDeniedHandler bean which is defined as an inner bean in this case. When an AuthenticationException occurs user is redirected to loginFormUrl defined in authenticationEntryPoint bean.

 

 

<bean id="filterSecurityInterceptor"

††††††††††† class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">

 

††††††††††† <property name="authenticationManager">

††††††††††††††††† <ref bean="authenticationManager" />

††††††††††† </property>

 

††††††††††† <property name="accessDecisionManager">

††††††††††††††††† <ref bean="accessDecisionManager" />

††††††††††† </property>

†††††††††††

††††††††††† <property name="objectDefinitionSource">

††††††††††††††††† <value>

††††††††††††††††††††††† CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

††††††††††††††††††††††† \A/c/portal/login\Z=ROLE_PORTAL_USER

††††††††††††††††††††††† \A/c/portal/logout\Z=ROLE_PORTAL_USER

††††††††††††††††††††††† \A/c/portal/layout.*\Z=ROLE_PORTAL_USER

††††††††††††††††††††††† \A/group/.*\Z=ROLE_PORTAL_USER

††††††††††††††††††††††† \A.*\Z=ROLE_ANONYMOUS,ROLE_PORTAL_USER

††††††††††††††††† </value>

††††††††††† </property>

†††††††††††

††††††††††† <property name="alwaysReauthenticate">

††††††††††††††††† <value>true</value>

††††††††††† </property>

 

</bean>

 

The last filter in the chain is filterSecurityInterceptor. It performs authorization of each web request. Actually real work is done by accessDecisionManager. Who is allowed to reach at what resources are given as objectDefinitionSource. Acegi has a very powerful URL pattern mechanism to define secured web resources. Letís review our protected resources here quickly:

 

/c/portal/login=ROLE_PORTAL_USER

/c/portal/logout=ROLE_PORTAL_USER

/c/portal/layout.*=ROLE_PORTAL_USER

/group/.*=ROLE_PORTAL_USER

.*=ROLE_ANONYMOUS,ROLE_PORTAL_USER

 

As you see, requests to login and logout URLs are protected and only allowed for users having role named ROLE_PORTAL_USER. Liferay only redirects unauthenticated users to its login URL, and this means that they are unauthenticated by Acegi, either. Acegi will send that users to our custom login page, instead of Liferayís login page, and force them to login before letting them reach at login URL of Liferay. When they are authenticated, Liferay redirects them to its main entry page.

 

Many of Liferay requests have a prefix of /c/portal/layout.* and /group/.*. Liferay expects those who want to reach at resources with that prefix to be authenticated. Therefore their users are expected to have role ROLE_PORTAL_USER to be able to access them. Any unauthenticated requests will first be redirected to Acegi login and then to the target URL only after successful authentication. We let all other remaining requests to be reached with any user. ROLE_ANONYMOUS identifies unauthenticated users.

 

I would like to note that filterSecurityInterceptor tries to authenticate users before every request check. You can configure that behavior with alwaysReauthenticate property. This may cause slow performance unless you donít cache your user info queried by Acegi from corresponding user realm. Acegi has EHCache suppport by default for such needs.

 

It is time to explain some other bean configurations apart from filters. As I said above, authenticationProcessingFilter handles authentication, and filterSecurityInterceptor handles protecting URL resources, but they donít do actual work. Most of the real work is done by authenticationManager, and accessDecisionManager beans.

 

††††† <bean id="authenticationManager"

††††††††††† class="org.acegisecurity.providers.ProviderManager">

 

††††††††††† <property name="providers">

††††††††††††††††† <list>

††††††††††††††††††††††† <ref bean="${security.auth.provider.bean}" />

††††††††††††††††††††††† <ref bean="anonymousAuthenticationProvider" />

††††††††††††††††††††††† <ref bean="testingAuthenticationProvider" />

††††††††††††††††† </list>

††††††††††† </property>

 

††††† </bean>

 

When a request to /j_acegi_security_check URL comes in, authenticationProcessingFilter enter into scene, gets username (j_username request parameter by default) and password (j_password request parameter by default) from web request, creates an Authentication token and calls authenticationManager to perform actual authentication. authenticationManager bean has several providers, and asks each one if they are able to authenticate current Authentication token until one of them accepts it.

 

There are currently two possibilities for authentication provider. One is ldapAuthenticationProvider, and the other is daoAuthenticationProvider. ldapAuthenticationProvider queries users from LDAP, and daoAuthenticationProvider queries them from an object which implements org.acegisecurity.userdetails.UserDetailsService. We currently use in memory for testing purposes, but you can always configure other kinds of UserDetailsService, such as JdbcDaoImpl. You can specify what kind of authenticationProvider to use through project.dev.extension.properties file. You need to create a project.dev.extension.properties and drop it into WEB-INF/classes folder of your Liferay installation in order to override default configuration which is for LDAP. There are two properties which are used to specify which authentication provider to use (security.auth.provider.bean), and from where users should be fetched when populating them to Liferay (security.auth.liferayUserServiceStrategy.bean).

 

security.auth.provider.bean=daoAuthenticationProvider

security.auth.liferayUserServiceStrategy.bean=inMemoryLiferayUserServiceStrategy

 

If you want to use daoAuthenticationProvider and inMemoryLiferayUserServiceStrategy, then you will also need to create acegi.userMap.properties and liferay.userMap.properties files in this folder as well. Those files should contain user information for Acegi authentication, and Liferay user population consecutively. For example, in acegi.userMap.properties each user is defined with its credentials and authorities, and Acegi will use them during authentication.

 

acegi.userMap.properties file sample content;

 

kenan=af35af457773d8636db03291677fa77dd25b631b, ROLE_PORTAL_USER

 

 

In liferay.userMap.properties file, we must have additional information for those users defined in acegi.userMap.properties file. That information is composed of firstname,surname,e-mail separated with commas. After a successful authentication inMemoryLiferayUserServiceStrategy Liferay.userMap.properties, and creates the user if it canít find a corresponding entry in Liferay database. Usernames which are key elements in both files must match with each other. The other fields for acegi.userMap.properties are encrypted password and comma separated roles user posses.

 

liferay.userMap.properties file sample content;

 

kenan=Kenan,Sevindik,ksevindik@gmail.com

 

When our web request is authenticated, the second step is authorization of it. As we mentioned above filterSecurityInterceptor is configured to authorize URL resources. Actually, it asks its configured accessDecisionManager bean to authorize current user.

 

 

††††† <bean id="accessDecisionManager"

††††††††††† class="org.acegisecurity.vote.AffirmativeBased">

 

††††††††††† <property name="allowIfAllAbstainDecisions">

††††††††††††††††† <value>false</value>

††††††††††† </property>

 

††††††††††† <property name="decisionVoters">

††††††††††††††††† <list>

††††††††††††††††††††††† <ref bean="roleVoter" />

††††††††††††††††† </list>

††††††††††† </property>

 

††††† </bean>

 

††††† <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter" />

 

Acegi Security Framework provides several different AccessDecisionManager types, each for different purposes. We configured an AffirmativeBased accessDecisionManager bean here, which grants access if any of its decisionVoter objects returns an affirmative response.

Liferay auto.login.hooks

Liferay portal provides a separate hooking mechanism to fetch valid credentials from any third party security system. Inside this hook method, we have to populate current user info to Liferay if that userís information doesnít exist before.

 

In Liferay 4.2.2, we donít have to configure this hook, and use Acegi SecurityContextHolderAwareRequestFilter instead to inform Liferay about authenticated user. However, we canít use SecurityContextHolderAwareRequestFilter in Liferay 4.3.2 portal.

 

Please note that if you use SecurityContextHolderAwareRequestFilter, hooking will never get into action, because Liferay wonít continue authentication process as it will receive a valid username from getRemoteUser() method. This causes error in Liferay 4.3.2. You must remove the filter from FilterChainProxy list.

 

There is an available com.liferay.portal.security.auth.AutoLogin interface to create auto login hooks in Liferay. It has a one method called login which takes HttpServletRequest, HttpServletResponse as input parameters and return userís credentials as String [].

 

In the login method, we get Authentication object from SecurityContext, and query user object from Liferay database, and return credentials String [] constructed with attributes from user found. As you see, if user doesnít exist we just create it in Liferay database. All of the source code and configuration files are available as attachment. You can examine addUser method in detail.

 

In order to configure auto.login.hooks, there is an auto.login.hooks property in portal.properties file, which can be overridden in ROOT/WEB-INF/classes/portal-ext.properties. For Liferay 4.3.2, auto.login.hooks configuration can be like as follows;

 

auto.login.hooks= org.acegisecurity.portlet.liferay.auth.AcegiAutoLoginHook43

 

If you look at the source code of AcegiAutoLoginHook43, you will see that it is only a wrapper around actual AcegiAutoLoginHook object, and it just instantiates InMemoryLiferayUserServiceStrategy class by default, set it to that auto login hook object, and delegates all the real work to the actual hook. If you want to fetch users from a different source, you will need to implement a LiferayUserServiceStrategy for it and provide it to AcegiAutoLoginHook instance.

 

When we type http://localhost:8080/web/guest/home, Acegi lets us to reach at the page as /web/guest/home is defined accessible by anonymous users.

 

 

 

 

Letís click Sign In on the top left corner. As you see below, we have reached at our custom login page instead of Liferay login page. This is because when we click at Sign In link, Liferay redirects us to /c/portal/login URL which is currently protected by Acegi; it is only allowed to access by users having roles ROLE_PORTAL_USER. As current web request is anonymous, that is unauthenticated Acegi sends us to its own login page for authentication.

 

 

 

 

We typed username and password, and clicked Login. As this is the first time for testuser to log into Liferay Portal, it showed us its ďTerms of UseĒ page. This means we have successfully logged into Liferay Portal!

 

Populating Acegi SecurityContext during Liferay Login Post Event

 

Alternative to delegating authentication to Acegi Security is to use Liferay login post event mechanism. In this method authentication is performed by Liferay itself. After successfull authentication Liferay calls void run(HttpServletRequest request, HttpServletResponse response) method of classes which extend from com.liferay.portal.struts.Action base class. Those classes are listed as comma separated in login.events.post property of portal.properties file. We can overwrite that property in portal-ext.properties and add new Action classes into the list which will be invoked if successful Liferay authentication occurs.

 

Acegi-portlet for Liferay has AcegiAuthenticationPostAction class which fetches authenticated user id from current request with calling request.getRemoteUser() and queries Liferay using LiferayUserServiceStrategy to get its User object. Once a valid User object is found, it loads corresponding Acegi UserDetails object using userDetailsService bean configured in Spring ApplicationContext. When a valid UserDetails object is returned from userDetailsService it creates a UsernamePasswordAuthentication token using information from UserDetails object and places it into SecurityContextHolder of Acegi.

 

You can configure Liferay to use AcegiAuthenticationPostAction class by adding following line into your ROOT/WEB-INF/classes/portal-ext.properties file.

login.events.post=org.acegisecurity.portlet.liferay.auth.AcegiAuthenticationPostAction,com.liferay.portal.events.LoginPostAction,com.liferay.portal.events.DefaultLandingPageAction

 

Moreover, you also need to enable Spring to create ApplicationContext when Liferay starts and configure Acegi Security Filter so that it will keep SecurityContext alive between requests. This configuration is already explained in first part of Delegating Liferay Authentication to Acegi Security Framework section above.

Propagating Acegi SecurityContext from Liferay Portal to Portlet Web Applications

I will give details about the second part of the solution now. The second part is about propagating SecurityContext from portal to individual portlets. As I mentioned at the Solution section, each portlet container has its own way of invoking their portlets. They usually perform inter-ServletContext communication with a well known servlet instance in those portlet web applications.

Intercepting Portlet Calls

 

Liferay caches each deployed portlet instance by wrapping them with its com.liferay.portlet.CachePortlet class, and each call to processAction and render methods always passes from those CachePortlet instances. I created an aspect to intercept those processAction and render methods and placed currently available SecurityContext object into HttpServletRequest object with key HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY. †It is possible to access actual HttpServletRequest instances from Liferayís own implementation of PortletRequest classes. We put SecurityContext instance into both HttpServletRequest and PortletRequest in order to support calls that are not intercepted on the portlet side.

 

before() : execution(

public void com.liferay.portlet.CachePortlet.processAction(..))

{

†††††††††††

††††††††††† Object[] args = thisJoinPoint.getArgs();

††††††††††† ActionRequest request = (ActionRequest) args[0];

††††††††††† ActionRequestImpl actionReqImpl = (ActionRequestImpl) request;


††††††††††† actionReqImpl.getHttpServletRequest().setAttribute(†††††††††††††††††††††††† †††††††††††††††††††††††††††† ††††† HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,

††††††††††††††††††††††† SecurityContextHolder.getContext());

 

††††† ††††† request.setAttribute(

††††††††††††††††††††††† HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,

††††††††††††††††††††††† SecurityContextHolder.getContext());

 

††††† }

†††††

before() : execution(

public void com.liferay.portlet.CachePortlet.render(..))

{

†††††††††††

††††††††††† Object[] args = thisJoinPoint.getArgs();

††††††††††† RenderRequest request = (RenderRequest)args[0];

††††††††††† RenderRequestImpl renderReqImpl = (RenderRequestImpl)request;

††††††††††† renderReqImpl.getHttpServletRequest().setAttribute(

††††††††††††††††††††††† HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,

††††††††††††††††††††††† SecurityContextHolder.getContext());

 

††††††††††† request.setAttribute(

††††††††††††††††††††††† HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,

††††††††††††††††††††††† SecurityContextHolder.getContext());

††††† }

 

In Liferay 4.2.2, CachePortlet class is contained in portal-ejb.jar. As most of the things, this is also changed in Liferay 4.3.2. The new jar is portal-impl.jar. We need to weave that jar with AspectJ compiler and replace that weaved jar with old one in ROOT/WEB-INF/lib folder.

 

††††† <target name="weave-portal-ejb">

††††††††††† <iajc debug="true"

sourceroots="${aspects-src-code-dir}/com/liferay/portal/security/aspects"

††††††††††††††††† inpath="portal-ejb.jar"

††††††††††††††††† outjar="portal-ejb-weaved.jar">

 

††††††††††††††††† <classpath refid="all-lib" />

††††††††††††††††† <classpath>

††††††††††††††††††††††† <fileset dir="D:/work/tools/liferay-portal-tomcat/webapps/ROOT/WEB-INF/lib">

†††††††††††††††††††††††††††† <include name="*.jar"/>

††††††††††††††††††††††† </fileset>

††††††††††††††††† </classpath>

††††††††††† </iajc>

††††† </target>

Retrieving SecurityContext on Portlet Side

 

On portlet web application side we need to extract that request attribute and place it again into SecurityContextHolder. In order to do this we extend Liferayís com.liferay.portal.kernel.servlet.PortletServlet because this is the class of well known servlet instances defined in portlet web applications.

 

public class SecurityContextAwarePortletServlet extends PortletServlet {

 

††††† private Log logger = LogFactory.getLog(getClass());

 

††††† public void service(HttpServletRequest req, HttpServletResponse res)

††††††††††††††††† throws IOException, ServletException {

 

††††††††††† initializeSecurityContext(req);

††††††††††† super.service(req, res);

††††† }

 

††††† private void initializeSecurityContext(HttpServletRequest req) {

††††††††††† try {

††††††††††††††††† Object obj = req.getAttribute(

HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);

 

††††††††††††††††† if(obj != null) {

††††††††††††††††††††††† obj = serializeDeserialize(obj);

††††††††††††††††††††††† if(logger.isDebugEnabled()) {

†††††††††††††††††††††††††††† logger.debug(

"SecurityContext object found :" + obj);

††††††††††††††††††††††† }††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††† ††††† SecurityContextHolder.setContext(

(SecurityContext)obj);

††††††††††††††††† } else {

††††††††††††††††††††††† if(logger.isDebugEnabled()) {

†††††††††††††††††††††††††††† logger.debug(

"SecurityContext object not found.");

††††††††††††††††††††††† }

††††††††††††††††† }

††††††††††† } catch (Exception ex) {

††††††††††††††††† logger.error(

"Exception during security context processing", ex);

††††††††††† }

††††† }

†††††

††††† private Object serializeDeserialize(Object obj)

throws IOException, ClassNotFoundException {

††††††††††† ObjectInputStream in = null;

††††††††††† ObjectOutputStream out = null;

††††††††††† try {

††††††††††††††††† ByteArrayOutputStream bout = new ByteArrayOutputStream();

††††††††††††††††† out = new ObjectOutputStream(bout);

††††††††††††††††† out.writeObject(obj);

††††††††††††††††† ByteArrayInputStream bin =

new ByteArrayInputStream(bout.toByteArray());

††††††††††††††††† in = new ObjectInputStream(bin);

††††††††††††††††† obj = in.readObject();

††††††††††††††††† return obj;

††††††††††† } finally {

††††††††††††††††† try {

††††††††††††††††††††††† out.close();

††††††††††††††††† } catch(Exception ex) {}

††††††††††††††††† try {

††††††††††††††††††††††† in.close();

††††††††††††††††† } catch(Exception ex) {}

††††††††††† }

††††† }

}

 

The purpose of serializeDeserialize above is to get rid of class loading problems as each portlet web application and Liferay itself own their own copy of acegi-security.jar in their classpaths.

Retrieving SecurityContext When Ajax Calls Involved

There are might be some cases in which web requests donít pass through SecurityContextAwareServlet. IceFaces Ajax Components are an example for such scenario. Except first rendering of portlet which uses IceFaces components, consecutive calls that originate from IceFaces ajax components goes directly, without passing through SecurityContextAwareServlet, to their final destinations, which are IceFaces specific servlets.

 

In such a scenario we need to populate SecurityContext with some other mechanism. As I said during explanation of CachePortlet, we put SecurityContext into current portlet request as well. If your view technology is JSF like in IceFaces case, then we have a solution. We have introduced a SecurityContextSessionIntegrationPhaseListener† which queries current web request for SecurityContext during restore view phase if only that request is PortletRequest, and saves SecurityContext object if it is found in the current request. That way, we are able to retrieve any available SecurityContext in the current web request, and place it into SecurityContextHolder on portlet side.

Quick Start with Liferay 4.3.x

Using Login Post Event Mechanism

††††††††††††††††††††††† <param-name>contextConfigLocation</param-name>

††††††††††††††††††††††† <param-value>

††††††††††††††††† †††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-core.xml†††

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-auth.xml

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-ldap.xml

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-4.3-login.post.event.xml

††††††††††††††††††††††† </param-value>

††††††††††† </context-param>

 

††††††††††† <listener>

††††††††††††††††††††††† <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

††††††††††† </listener>

 

††††††††††† <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>

 

Be careful that, AcegiSecurityFilter mapping should be the first one among other filter mappings in web.xml file.

Using Auto Login Hook Mechanism

††††††††††††††††††††††† <param-name>company_id</param-name>

††††††††††††††††††††††† <param-value>1</param-value>

</context-param>
<context-param>

††††††††††††††††††††††† <param-name>contextConfigLocation</param-name>

††††††††††††††††††††††† <param-value>

†††††††††††††††† ††††††††††††††††† †classpath:/appcontext/spring-beans.acegi-portlet-liferay-core.xml†††

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-auth.xml

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-ldap.xml

†††††††††††††††††††††††††††††††††† classpath:/appcontext/spring-beans.acegi-portlet-liferay-4.3-auto.login.hook.xml

††††††††††††††††††††††† </param-value>

††††††††††† </context-param>

 

††††††††††† <listener>

††††††††††††††††††††††† <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

††††††††††† </listener>

 

††††††††††† <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>

 

Be careful that, AcegiSecurityFilter mapping should be the first one among other filter mappings in web.xml file.

 

Quick Start with Liferay 4.2.x

Using AutoLogin Hook Mechanism

 

It is not much different than Liferay 4.3.x configuration. There are only three places in corresponding 4.3.x configuration, you need to change for 4.2.x;

 

Configuring Acegi-Portlet To Use LDAP

 

By default Acegi portlet uses daoAuthenticationProvider as AuthenticationProvider and inMemoryDaoImpl as userDetailsService. The other option is to use ldapAuthenticationProvider and ldapUserDetailsService. If you want to change default configuration, you need to add following lines into project.extension.properties file.

 

security.auth.provider.bean=ldapAuthenticationProvider

security.auth.userDetailsService.bean=ldapUserDetailsService

 

You will also need to configure Liferay to use LDAP as its user realm, and change security.auth.liferayUserServiceStrategy.bean property to ldapLiferayUserServiceStrategy.

 

security.auth.liferayUserServiceStrategy.bean=ldapLiferayUserServiceStrategy

 

Finally you will need to change default LDAP connection settings;

 

security.ldap.provider.url=ldap://localhost:389/dc=ksevindik,dc=com

security.ldap.manager.dn=cn=root,dc=ksevindik,dc=com

security.ldap.manager.password=ksevindik

security.ldap.userSearch.baseDn=

security.ldap.userSearch.filter=(uid={0})

security.ldap.userSearch.searchSubTree=true

security.ldap.auth.userDnPattern=cn={0}

security.ldap.auth.group.searchBase=ou=groups

security.ldap.auth.group.roleAttribute=ou