JSF/Acegi 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 (note that this is spring-2, this introduces new scopes like e.g. “session”):
<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">
<!--
The AffirmativeBased voter allows access if at least one voter votes
to grant access. Use the UnanimousBased voter if you only want to
grant access if no voter votes to deny access. -->
<bean class="org.acegisecurity.vote.AffirmativeBased">
<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>
<!--
The authenticated voter grant access if e.g.
IS_AUTHENTICATED_FULLY is an attribute -->
<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/pages/**=IS_AUTHENTICATED_FULLY
/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" scope="session">
<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">
<span jsfc="h:messages"/><br/>
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>