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>

Sunday, July 02, 2006

Finally, i have a blog, too. And that only because i wanted to comment some blog entry.
This is my first entry, and to be honest, it's only for testing. Nothing will be said here - really.