Updated Siteminder auth processing filter and added test case. As of this weekend, this version is in production at a large financial org.

This commit is contained in:
Scott McCrory 2005-09-25 22:48:33 +00:00
parent 9b898e84c4
commit 4717b64b83
2 changed files with 260 additions and 79 deletions

View File

@ -10,48 +10,43 @@ import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.ui.WebAuthenticationDetails; import net.sf.acegisecurity.ui.WebAuthenticationDetails;
/** /**
* Extends Acegi's AuthenticationProcessingFilter to use Siteminder's headers for identification. * Extends Acegi's AuthenticationProcessingFilter to pick up Netegrity
* Siteminder's headers.
* *
* <P> * <P>
* Provides the ability set all source key names and also provides form-based authentication as a backup. * Also provides a backup form-based authentication and the ability set source
* key names.
* </P> * </P>
* *
* <P> * <P>
* You must set the Siteminder header keys, otherwise Siteminder checks will be skipped (there * <B>Siteminder</B> must present two <B>headers</B> to this filter, a
* are no defaults). If the <I>Siteminder</I> check is unsuccessful or if the headers are not * username and password. You must set the header keys before this filter is
* defined/found in the HTTP request, then the <I>form</I> parameters will be checked (see below). * used for authentication, otherwise Siteminder checks will be skipped. If the
* This allows applications to function even when their Siteminder infrastructure * Siteminder check is unsuccessful (i.e. if the headers are not found), then
* is unavailable, as is often the case during development. If you do not wish to use backup * the form parameters will be checked (see next paragraph). This allows
* form-based authentication, then set the form parameter keys to null/blank. * applications to optionally function even when their Siteminder infrastructure
* is unavailable, as is often the case during development.
* </P> * </P>
* *
* <P> * <P>
* <B>Siteminder</B> must present at least one HTTP <I>header</I> to this filter - typically * <B>Login forms</B> must present two <B>parameters</B> to this filter: a
* containing a unique identifier such a username, an employee number or a national ID. * username and password. If not specified, the parameter names to use are
* This makes sense because Siteminder has already <I>authenticated</I> the user prior to * contained in the static fields {@link #ACEGI_SECURITY_FORM_USERNAME_KEY} and
* getting to this filter, so we're really only using it for identification and not authentication. * {@link #ACEGI_SECURITY_FORM_PASSWORD_KEY}.
* Set the <code>siteminderUsernameHeaderKey</code> value to tell the filter where to greb the "username"
* value. You'll typically also set the <code>siteminderPasswordHeaderKey</code> to the same header key.
* Just remember to modify your AuthenticationDAO so that it can handle identity-only requests!
* </P> * </P>
* *
* <P> * <P>
* <B>Forms</B> must present two <I>parameters</I> to this filter: a username and a password. * <B>Do not use this class directly.</B> Instead, configure
* If not specified, the parameter names to use are defaulted to the static fields * <code>web.xml</code> to use the
* {@link #ACEGI_SECURITY_FORM_USERNAME_KEY} and {@link #ACEGI_SECURITY_FORM_PASSWORD_KEY}. * {@link net.sf.acegisecurity.util.FilterToBeanProxy}.
* </P>
*
* <P>
* <B>Do not use this class directly.</B> Instead, configure <code>web.xml</code>
* to use the {@link net.sf.acegisecurity.util.FilterChainProxy} and include this filter.
* </P> * </P>
* *
* @author <a href="mailto:scott@mccrory.us">Scott McCrory</a> * @author <a href="mailto:scott@mccrory.us">Scott McCrory</a>
* @author Ben Alex * @version CVS $Id: SiteminderAuthenticationProcessingFilter.java,v 1.6
* @since 0.9.0 * 2005/08/19 14:31:30 E099544 Exp $
* @version CVS $Id$
*/ */
public class SiteminderAuthenticationProcessingFilter extends AuthenticationProcessingFilter { public class SiteminderAuthenticationProcessingFilter extends
AuthenticationProcessingFilter {
/** /**
* Siteminder username header key. * Siteminder username header key.
@ -80,7 +75,7 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
super(); super();
} }
/*** /***************************************************************************
* This filter by default responds to <code>/j_acegi_security_check</code>. * This filter by default responds to <code>/j_acegi_security_check</code>.
* *
* @return the default * @return the default
@ -89,7 +84,11 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
return "/j_acegi_security_check"; return "/j_acegi_security_check";
} }
public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException { /**
* @see net.sf.acegisecurity.ui.AbstractProcessingFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest)
*/
public Authentication attemptAuthentication(HttpServletRequest request)
throws AuthenticationException {
String username = null; String username = null;
String password = null; String password = null;
@ -105,58 +104,76 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
} }
// If the Siteminder authentication info wasn't available, then get it from the form parameters // If the Siteminder authentication info wasn't available, then get it
if (username == null || username.length() == 0 || password == null || password.length() == 0) { // from the form parameters
if (username == null || username.length() == 0 || password == null
|| password.length() == 0) {
//System.out.println("Siteminder headers not found for authentication"); System.out
.println("Siteminder headers not found for authentication, so trying to use form values");
if (formUsernameParameterKey != null && formUsernameParameterKey.length() > 0) { if (formUsernameParameterKey != null
&& formUsernameParameterKey.length() > 0) {
username = request.getParameter(formUsernameParameterKey); username = request.getParameter(formUsernameParameterKey);
} } else {
else { username = request
username = request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY); .getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
} }
password = obtainPassword(request); password = obtainPassword(request);
} }
// If either are null, set them to blank to avoid a NPE. // Convert username and password to upper case. This is normally not a
if (username == null) { // good practice but we do it here because Siteminder gives us the username
// in lower case, while most backing systems store it in upper case.
if (username != null) {
username = username.toUpperCase();
} else {
// If username is null, set to blank to avoid a NPE.
username = ""; username = "";
} }
if (password == null) { if (password != null) {
password = password.toUpperCase();
} else {
// If password is null, set to blank to avoid a NPE.
password = ""; password = "";
} }
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property // Allow subclasses to set the "details" property
setDetails(request, authRequest); setDetails(request, authRequest);
// Place the last username attempted into HttpSession for views // Place the last username attempted into HttpSession for views
request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username); request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY,
username);
return this.getAuthenticationManager().authenticate(authRequest); return this.getAuthenticationManager().authenticate(authRequest);
} }
/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) throws ServletException {
} }
/*** /***************************************************************************
* Provided so that subclasses may configure what is put into the * Provided so that subclasses may configure what is put into the
* authentication request's details property. The default implementation * authentication request's details property. The default implementation
* simply constructs {@link WebAuthenticationDetails}. * simply constructs {@link WebAuthenticationDetails}.
* *
* @param request that an authentication request is being created for * @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have * @param authRequest the authentication request object that should have its details set
* its details set
*/ */
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(new WebAuthenticationDetails(request)); authRequest.setDetails(new WebAuthenticationDetails(request));
} }
/*** /***************************************************************************
* Enables subclasses to override the composition of the password, such as * Enables subclasses to override the composition of the password, such as
* by including additional values and a separator. * by including additional values and a separator.
* *
@ -176,10 +193,10 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
*/ */
protected String obtainPassword(HttpServletRequest request) { protected String obtainPassword(HttpServletRequest request) {
if (formPasswordParameterKey != null && formPasswordParameterKey.length() > 0) { if (formPasswordParameterKey != null
&& formPasswordParameterKey.length() > 0) {
return request.getParameter(formPasswordParameterKey); return request.getParameter(formPasswordParameterKey);
} } else {
else {
return request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY); return request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
} }

View File

@ -0,0 +1,164 @@
package net.sf.acegisecurity.ui.webapp;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.MockAuthenticationManager;
import net.sf.acegisecurity.ui.WebAuthenticationDetails;
import org.springframework.mock.web.MockHttpServletRequest;
/**
* Tests SiteminderAuthenticationProcessingFilter.
*
* @author Ben Alex
* @author <a href="mailto:scott@mccrory.us">Scott McCrory</a>
* @version CVS $Id$
*/
public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
/**
* Basic constructor.
*/
public SiteminderAuthenticationProcessingFilterTests() {
super();
}
/**
* Argument constructor.
*
* @param arg0
*/
public SiteminderAuthenticationProcessingFilterTests(String arg0) {
super(arg0);
}
/**
* @see junit.framework.TestCase#setUp()
*/
public final void setUp() throws Exception {
super.setUp();
}
/**
* Runs the tests as a command-line program.
*
* @param args
*/
public static void main(String[] args) {
junit.textui.TestRunner
.run(SiteminderAuthenticationProcessingFilterTests.class);
}
/**
* Tests the class' getters.
*/
public void testGetters() {
SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
assertEquals("/j_acegi_security_check", filter
.getDefaultFilterProcessesUrl());
}
/**
* Tests normal form processing.
*
* @throws Exception
*/
public void testFormNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request
.addParameter(
SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY,
"marissa");
request
.addParameter(
SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_PASSWORD_KEY,
"koala");
MockAuthenticationManager authMgr = new MockAuthenticationManager(true);
SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
filter.setAuthenticationManager(authMgr);
filter.init(null);
Authentication result = filter.attemptAuthentication(request);
assertTrue(result != null);
assertEquals("127.0.0.1", ((WebAuthenticationDetails) result
.getDetails()).getRemoteAddress());
}
/**
* Tests form null password handling.
*
* @throws Exception
*/
public void testFormNullPasswordHandledGracefully() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request
.addParameter(
SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY,
"marissa");
MockAuthenticationManager authMgr = new MockAuthenticationManager(true);
SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
filter.setAuthenticationManager(authMgr);
filter.init(null);
Authentication result = filter.attemptAuthentication(request);
assertTrue(result != null);
}
/**
* Tests form null username handling.
*
* @throws Exception
*/
public void testFormNullUsernameHandledGracefully() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request
.addParameter(
SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_PASSWORD_KEY,
"koala");
MockAuthenticationManager authMgr = new MockAuthenticationManager(true);
SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
filter.setAuthenticationManager(authMgr);
filter.init(null);
Authentication result = filter.attemptAuthentication(request);
assertTrue(result != null);
}
/**
* Tests normal Siteminder header processing.
*
* @throws Exception
*/
public void testSiteminderNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("SM_USER", "E099544");
MockAuthenticationManager authMgr = new MockAuthenticationManager(true);
SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
filter.setAuthenticationManager(authMgr);
filter.setSiteminderUsernameHeaderKey("SM_USER");
filter.setSiteminderPasswordHeaderKey("SM_USER");
filter.init(null);
Authentication result = filter.attemptAuthentication(request);
assertTrue(result != null);
assertEquals("127.0.0.1", ((WebAuthenticationDetails) result
.getDetails()).getRemoteAddress());
}
}