magrokosmos

Tuesday, July 04, 2006

JSF/Acegi authentication with a backing bean

STOP! This posting (the whole blog) is outdated, i moved to http://www.javakaffee.de/blog/. This posting you find now at http://www.javakaffee.de/blog/2006/07/04/jsfacegi-authentication-with-a-backing-bean/

Thanx to this blog post i managed to integrate Acegi 1.0.1 with JSF. Some minor modifications were necessary, because in this version of Acegi some things have changed.

The basic idea of this approach is to use a JSF backing bean that is responsible for authentication (that processes the "login" request from the user). This allows you not only to send the user to a success/error page, but you are also able to send him to a custom page if some condition is met.

The backing bean looks like the following:


package com.freiheit;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.context.HttpSessionContextIntegrationFilter;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.ui.WebAuthenticationDetails;
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

public final class AuthenticationController {

private static final Log LOG = LogFactory.getLog( AuthenticationController.class );

private String _username;
private String _password;

// injected properties
private AuthenticationManager _authenticationManager;

public String getPassword() {
return _password;
}

public void setPassword( String password ) {
_password = password;
}

public String getUsername() {
return _username;
}

public void setUsername( String userName ) {
_username = userName;
}

@SuppressWarnings("unchecked")
public String authenticate() {
String outcome = "failure";

try {
final String userName = getUsername();
final String password = getPassword();
final UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(
userName, password );

final HttpServletRequest request = getRequest();
authReq.setDetails( new WebAuthenticationDetails( request ) );

final HttpSession session = request.getSession();
session.setAttribute(
AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY,
userName );

/* perform authentication
*/
final Authentication auth = getAuthenticationManager().authenticate( authReq );

/* initialize the security context.
*/
final SecurityContext secCtx = SecurityContextHolder.getContext();
secCtx.setAuthentication( auth );
session.setAttribute( HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY, secCtx );

outcome = "success";

} catch ( Exception e ) {
outcome = "failure";
FacesContext.getCurrentInstance().addMessage( null, new FacesMessage( e.getMessage() ) );
}

return outcome;
}

public void logout( ActionEvent e ) {

final HttpServletRequest request = getRequest();
request.getSession( false ).removeAttribute( HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY );

/* simulate the SecurityContextLogoutHandler
*/
SecurityContextHolder.clearContext();

request.getSession( false ).invalidate();
}

private HttpServletRequest getRequest() {
return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
}

public AuthenticationManager getAuthenticationManager() {
return _authenticationManager;
}

@Required
public void setAuthenticationManager(
AuthenticationManager authenticationManager ) {
_authenticationManager = authenticationManager;
}

}


The spring configuration (applicationContext.xml) has the following entries:


<bean id="acegiFilterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,securityRequestFilter,exceptionTranslationFilter,filterSecurityInterceptor
</value>
</property>
<!--
/**=httpSessionContextIntegrationFilter,requestWrapper,filterSecurityInterceptor
-->
</bean>

<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="context">
<value>org.acegisecurity.context.SecurityContextImpl</value>
</property>
</bean>

<bean id="securityRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" />

<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl">
<value>/login.jsf</value>
</property>
<property name="forceHttps"><value>false</value></property>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage">
<value>/accessDenied.jsf</value>
</property>
</bean>
</property>
</bean>

<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter">
<!-- Reset the role prefix to "", default is ROLE_ -->
<property name="rolePrefix">
<value></value>
</property>
</bean>
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/pages/company/**=/permissions/permission1
/pages/**=/permissions/permission01
</value>
</property>
</bean>

<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider" />
</list>
</property>
</bean>

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<bean class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
mgrotzke=test123,/permissions/permission1,/permissions/permission01
skaiser=test123,/permissions/permission01
</value>
</property>
</bean>
</property>
</bean>

<bean id="authenticationController" class="com.freiheit.AuthenticationController">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
</bean>


The authenticationController bean is the backing bean (see above) that is called from the (jsp/facelets) page. It can be defined in the spring applicationContext.xml because the faces-config contains an entry for springs DelegatingVariableResolver:


<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application>


The web.xml has the following filter entry for acegi:


<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetBean</param-name>
<param-value>acegiFilterChainProxy</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>


The faces-config.xml contains the following navigation-rules for login and logout:

<navigation-rule>
<from-view-id>/login.xhtml</from-view-id>
<navigation-case>
<from-action>#{authenticationController.authenticate}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/pages/index.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-action>#{authenticationController.authenticate}</from-action>
<from-outcome>failure</from-outcome>
<to-view-id>/login.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-action>logout</from-action>
<to-view-id>/login.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>


The login page using facelets essentially has the following content:

<form jsfc="h:form" id="loginForm">
Username: <input jsfc="h:inputText" id="inputUsername" value="#{authenticationController.username}" /><br/>
Password: <input jsfc="h:inputSecret" value="#{authenticationController.password}" /><br/>
<input jsfc="h:commandButton" action="#{authenticationController.authenticate}"
value="Login" />
</form>


The logout button should be defined in an overall template, and would look like the following:

<a href="#" jsfc="h:commandLink" action="logout"
actionListener="#{authenticationController.logout}">#{msgs.logout}</a>

6 Comments:

  • Can't you just post the WHOLE example ? The little fragments just won't help much :( Maybe you can post the JSP page, etc. That will save us a lot of time, otherwise we spend quite sometime trying to understand the solution...
    Thanks in advance...

    By Anonymous Anonymous, at 7:40 PM  

  • Hello, i just added the relevant parts of the faces-config.xml and the login page, additionally an example for a logout-button.

    Notice that the logout method changed from an action method (String logout()) to an actionlistener (void logout(ActionEvent e)).

    I hope this helps,
    cheers,
    Martin

    By Blogger Martin Grotzke, at 10:13 PM  

  • I see here one important concurrency problem.

    authenticationController is defined in spring context as singleton, but since it
    isn't stateful, it can't be used as singleton.

    In fact, it has to be defined in faces-config.xml as session or request scoped managed bean, otherwise under even moderate load it will fail to work properly.

    ALternatively, special bean factory can be used to create scoped instances of authenticationController

    By Anonymous Anonymous, at 3:02 PM  

  • Hi Dmitry,

    i moved with the blog to http://www.javakaffee.de/blog/, this posting you find now at http://www.javakaffee.de/blog/2006/07/04/jsfacegi-authentication-with-a-backing-bean/.

    Your comment is very good! In just checked my applicationContext.xml and saw that there i have set the scope to session, but it's missing in this example. Sorry for that!!

    I updated the example at http://www.javakaffee.de/blog/2006/07/04/jsfacegi-authentication-with-a-backing-bean/, please do no longer refer to this one. I'll add a comment that this one is outdated now...

    Cheers,
    Martin

    By Blogger Martin Grotzke, at 3:58 PM  

  • Hello!
    I tried your example. And waht I don't understand is: why my authenticationManager returns always null? and why does it must not return null?
    Thanks for reading!

    By Anonymous Anonymous, at 9:11 AM  

  • Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now. Keep it up!
    And according to this article, I totally agree with your opinion, but only this time! :)

    By Anonymous Anonymous, at 12:26 PM  

Post a Comment

<< Home