if your application is part of multiple applications that share the same login screen and you need to just login once and be able to use any application without any other login you for sure need SSO, what do you think about CAS?
yes its a fascinating solutionbut for sure if your company is not an open source solution it will refuse deploying anything (cas.war) on tomcate.
if this was your case so this blog post is for you.
Now we will try to implement a trivial simple SSO implementation using Acegi.
The SSO will work as follows:
suppose we have two applications one called A, and the other called B.
Application A will have link to B and vice versa, and we have only one login page that is found in A.
we can implement the SSO in two ways either sending the username and password implicitly to the B application from A application offcourse you will encrypt the password.
or we will use SSO_REQUEST table that holds records for each navigation from to A or B
SSO_REQUEST structure is as follows:
-------------------------------------------------------------------------------------------------------------------------------------- | ID | DESTINATION | LOGIN_STATUS | LOGIN_DATE | REQUEST_DATE | USER_FK | -------------------------------------------------------------------------------------------------------------------------------------- | LONG | VARCHAR | VARCHAR | TIMESTAMP | TIMESTAMP | LONG | --------------------------------------------------------------------------------------------------------------------------------------
this table will hold record when a user try to navigate from A to B or vice versa and the information that this table holds are
- ID is the id of the created SSO_REQUEST
- DESTINATION is the destination Application (A or B)
- LOGIN_STATUS is the status of the SSO_REQUEST wither it is NEW_REQUEST or SUCCESS_LOGIN
Now place Link for B inside form that submit the SSO_REQUEST.ID to our CustomAuthenticationProcessingFilter as follows:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %>
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=UTF-8" />
<title>EPS - Login</title>
<link rel="stylesheet" type="text/css" href="/css/menu.css" />
<link rel="stylesheet" type="text/css" href="/css/main.css" />
</head>
<body>
<c:if test="${not empty param.login_error}">
<font color="red">
Your login attempt was not successful, please try again.<br/> <br/>
<%--= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() --%>
</font>
</c:if>
<form action="<c:url value='http://10.1.119.76:8889/eps/j_acegi_security_check'/>" method="post">
<table border="0" cellpadding="0" cellspacing="2" bgcolor="#ffffff">
<tr>
<td style="border: 1px solid #000">
<table border="0" cellspacing="1" cellpadding="4" bgcolor="#D4D0C8">
<tr bgcolor="#000000">
<td valign="top"><font color="#D4D0C8"><b>Sign In</b></font></td>
</tr>
<tr valign="top">
<td>
<b>User Name</b><br>
<input type="text" name="j_username" maxlength="25" size="25" <c:if test="${not empty param.login_error}">value='<%= session.getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY) %>'</c:if>>
<p><b>Password</b><br>
<input type="password" name ="j_password" maxlength="25" size="25">
<p>
<b><input type="checkbox" name="_acegi_security_remember_me">Don't ask for my password for two weeks
</p>
<input type="HIDDEN" name ="j_sso_id" maxlength="25" size="25" value="1">
<input class="contrack_button" type="submit" name="Submit" value="Sign In" >
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>
CustomAuthenticationProcessingFilter is mainly used to implement the custom SSO as when the user will be logged in from other system then it will try to access this system it will post the j_username, j_password, and j_sso_id. and it will use the j_sso_id to load the associated SSO_REQUEST record and check if it is a valid request or not if true it will create the authentication Request and make the authenticaion against the authentication manager.
By this we will be simulationg the minimum SSO functionality and acegi will be employeed and this system will be logged in to the J2EE container.
First: implement the CustomAuthenticationProcessingFilter.java that extends AuthenticationProcessingFilter as follows:
package ae.dxbpolice.eps.business.service.security.filter;
import ae.dxbpolice.eps.business.ServiceLocator;
import ae.dxbpolice.eps.business.datatype.DestinationApplication;
import ae.dxbpolice.eps.business.datatype.LoginStatus;
import ae.dxbpolice.eps.business.vo.sso.SingleSignOnVO;
import ae.dxbpolice.eps.business.service.security.exception.SSOAuthenticationException;
import java.io.IOException;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
/**
* <b>Description:</b>
* CustomAuthenticationProcessingFilter is mainly used to implement the custom SSO
* as when the user will be logged in from other system then it will try to access
* this system it will post the j_username, j_password, and j_sso_id. and it will
* use the j_sso_id to load the associated SSO_REQUEST record and check if it is
* a valid request or not if true it will create the authentication Request and
* make the authenticaion against the authentication manager.
*
* By this we will be simulationg the minimum SSO functionality and acegi will be
* employeed and this system will be logged in to the J2EE container.
*
* Copyright 2008 I-Soft.
*
* @author <a href="mailto:robinhoo.2006@gmail.com">Ali Abdel-Aziz</a>
* @version 1.0
* @since 21/05/2008
*/
public class CustomAuthenticationProcessingFilter extends
AuthenticationProcessingFilter {
// Static fields/initializers =====================================================================================
public static final String ACEGI_SECURITY_FORM_SSO_ID_KEY = "j_sso_id";
/**
* overided to make any special processing different than the normal behaviour.
* like loading the username, password or both from the database if they are
* not sent.
*
* @throws org.acegisecurity.AuthenticationException
* @return Authentication
* @param request
*
* @author Ali Abdel-Aziz
*/
public Authentication attemptAuthentication(HttpServletRequest request)
throws AuthenticationException {
// String username = obtainUsername(request);
// String password = obtainPassword(request);
String ssoId = obtainSSOId(request);
if (ssoId == null) {
ssoId = "-1";
}
ssoId = ssoId.trim();
SingleSignOnVO ssoVO = ServiceLocator.instance().getSecurityService().getSpecificSSORequest(Long.valueOf(ssoId));
String username = obtainUsername(ssoVO);
String password = obtainPassword(ssoVO);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Place the last username attempted into HttpSession for views
request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
Authentication authentication = this.getAuthenticationManager().authenticate(authRequest);
if(authentication != null ) {
this.onPostAuthentication(request);
}
return authentication;
}
/**
*
* @throws java.io.IOException
* @throws org.acegisecurity.AuthenticationException
* @param request
*
* @author Ali Abdel-Aziz
*/
protected void onPreAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException,IOException {
if (!validAuthenticationRequest(request,response)) {
// this exception will be caught from acegi-xml and will be redirected to the login page.
throw new SSOAuthenticationException();
}
}
/**
* change the SSO_REQUEST login status from NEW_REQUEST to SUCCESS_LOGIN
*
* @throws java.lang.Exception
* @param response
* @param request
*/
protected void onPostAuthentication(HttpServletRequest request)
throws AuthenticationException {
String ssoId = obtainSSOId(request);
if (ssoId == null) {
ssoId = "-1";
}
ssoId = ssoId.trim();
SingleSignOnVO ssoVO = ServiceLocator.instance().getSecurityService().getSpecificSSORequest(Long.valueOf(ssoId));
if(ssoVO != null && ssoVO.getLoginStatus().equals(LoginStatus.NEW_REQUEST)) {
ssoVO.setLoginStatus(LoginStatus.SUCCESS_LOGIN);
ssoVO.setLoginDate(new Date());
ServiceLocator.instance().getSecurityService().updateSSORequest(ssoVO);
}
}
/**
* ï‚§ EPS system will validate the SSO parameters sent by EMS system as following: -
* o User ID, SSO ID, work group ID, and position ID must have
* a related record in SSO table with destination field equals EPS.
* o Request status must be 1 (new request).
* o Login date must be null.
* o Request date must be less than current date.
* o Difference between current date and request date must not exceed
* SSO timeout value (configurable) which means that the user request must be received within few seconds or it will be rejected.
*
* @return
* @param response
* @param request
*
* @author Ali Abdel-Aziz
*/
private boolean validAuthenticationRequest(HttpServletRequest request, HttpServletResponse response) {
String ssoId = obtainSSOId(request);
if (ssoId == null || ssoId.equalsIgnoreCase("")) {
return false;
}
ssoId = ssoId.trim();
SingleSignOnVO ssoVO = ServiceLocator.instance().getSecurityService().getSpecificSSORequest(Long.valueOf(ssoId));
if(ssoVO != null && ssoVO.getLoginStatus().equals(LoginStatus.NEW_REQUEST) &&
ssoVO.getLoginDate() == null && ssoVO.getDestination().equals(DestinationApplication.EPS)) {
return true;
} else {
return false;
}
}
/**
* @param request so that request attributes can be retrieved
*
* @return the j_sso_id that will be used to handle the Single SignOn by
* verifying that there is record in the SSO_REQUEST table matching the j_sso_id.
*
*/
protected String obtainSSOId(HttpServletRequest request) {
return request.getParameter(ACEGI_SECURITY_FORM_SSO_ID_KEY);
}
/**
* Enables subclasses to override the composition of the password, such as by including additional values
* and a separator.<p>This might be used for example if a postcode/zipcode was required in addition to the
* password. A delimiter such as a pipe (|) should be used to separate the password and extended value(s). The
* <code>AuthenticationDao</code> will need to generate the expected password in a corresponding manner.</p>
*
* @param request so that request attributes can be retrieved
*
* @return the password that will be presented in the <code>Authentication</code> request token to the
* <code>AuthenticationManager</code>
*/
protected String obtainPassword(SingleSignOnVO ssoVO) {
return ServiceLocator.instance().getSecurityService().getUserDetails(ssoVO.getUser().getUsername()).getPassword();
}
/**
* Enables subclasses to override the composition of the username, such as by including additional values
* and a separator.
*
* @param ssoVO so that SingleSignOnVO attributes can be retrieved
*
* @return the username that will be presented in the <code>Authentication</code> request token to the
* <code>AuthenticationManager</code>
*/
protected String obtainUsername(SingleSignOnVO ssoVO) {
return ssoVO.getUser().getUsername();
}
}
Second:
Now configure applicationContext-acegiSecurity.xml to use CustomAuthenticationProcessingFilter as follows:
Â<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!--bean id="myUserDetailsService" class="com.mycompany.MyUserDetailsServiceImpl"
< get access to the user entity >
<property name="userDao"><ref bean="userDao"/></property>
</bean-->
<!-- UserDetailsService is the most commonly frequently Acegi Security interface implemented by end users -->
<bean id="userDetailsService" class="ae.dxbpolice.eps.business.service.UserDetailsServiceImpl"/>
<!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"><ref bean="userDetailsService"/></property>
</bean>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<!-- Start Acegi Configuration for web -->
<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,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/index.jsp"/><!-- URL redirected to after logout -->
<constructor-arg>
<list>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
<!--bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"-->
<bean id="authenticationProcessingFilter" class="ae.dxbpolice.eps.business.service.security.filter.CustomAuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<!--property name="authenticationFailureUrl" value="/jsps/login/login-form.jsp?login_error=1"/-->
<property name="authenticationFailureUrl" value="http://10.1.119.76:8080/A/login.jsp?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="exceptionMappings">
<props>
<prop key="ae.dxbpolice.eps.business.service.security.exception.SSOAuthenticationException">
http://10.1.119.76:8080/A/login.jsp?login_error=1
</prop>
</props>
</property>
</bean>
Â
<bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
<bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="changeThis"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<!--property name="loginFormUrl" value="/jsps/login/login-form.jsp"/-->
<property name="loginFormUrl" value="/jsps/login/login-redirect.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/jsps/login/access-denied.jsf"/>
</bean>
</property>
</bean>
<!-- Authorization in Acegi Security is performed mainly by the FilterSecurityInterceptor filter.
This filter identifies a user-role relationship for a URL.
-->
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<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="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter">
<!-- Reset the role prefix to "EPS_", default is ROLE_ -->
<property name="rolePrefix">
<value>EPS_</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>
<!-- Start the Dinamic URL-ROLe Mapping -->
<property name="objectDefinitionSource">
<ref local="dbDrivenFilterInvocationDefinitionSource" />
<!--value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/layout/**=IS_AUTHENTICATED_ANONYMOUSLY,IS_AUTHENTICATED_REMEMBERED
/styles/**=IS_AUTHENTICATED_ANONYMOUSLY
/jsps/login/login-form.jsp=IS_AUTHENTICATED_ANONYMOUSLY
/jsps/login/access-denied=IS_AUTHENTICATED_REMEMBERED
/j_acegi_security_check=IS_AUTHENTICATED_ANONYMOUSLY
/jsps/procedure/**=IS_AUTHENTICATED_REMEMBERED
/jsps/linkrole/**=EPS_SYSTEM_ADMIN
</value-->
</property>
</bean>
<bean id="dbDrivenFilterInvocationDefinitionSource"
class="ae.dxbpolice.eps.business.service.DatabaseDrivenFilterInvocationDefinitionSource">
<property name="securityService">
<!--ref local="authorizationService" /-->
<ref bean="securityService" />
</property>
</bean>
<bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="key" value="changeThis"/>
</bean>
<!-- End Acegi Configuration for web -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:/ehcache-failsafe.xml</value>
</property>
</bean>
</beans>
and this is the code for the login-redirect.jsp page I make this page redirect to the login URL instead of writting the login URL directly as this attribute expect relative URLs
ÂÂÂ<%response.sendRedirect("http://10.1.119.76:8080/ems/index.jsp");%>
the XML configuration files, jsps and the Custom Java Classes are attached.
Reference:
http://www.modlost.net/home/articles/programming/acegi-security-custom-authentication.html
| Attachment | Size |
|---|---|
| applicationContext-acegiSecurity.xml | 17.42 KB |
| SSOAuthenticationException.java | 485 bytes |
| A.rar | 639.84 KB |
| CustomAuthenticationProcessingFilter.java | 8.3 KB |

Recent comments
1 week 4 hours ago
2 weeks 1 day ago
2 weeks 3 days ago
2 weeks 5 days ago
3 weeks 2 hours ago
3 weeks 3 days ago
3 weeks 3 days ago
5 weeks 2 days ago
10 weeks 4 hours ago
10 weeks 5 hours ago