Browse Source

OPEN - issue SEC-657: Create pre-authenticated processing filter which obtains username from request header
http://jira.springframework.org/browse/SEC-657. Added filter and test class.

Luke Taylor 17 years ago
parent
commit
c9b6fe9555

+ 8 - 8
core/src/main/java/org/springframework/security/ui/preauth/AbstractPreAuthenticatedProcessingFilter.java

@@ -63,11 +63,11 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends SpringSec
     /**
      * Do the actual authentication for a pre-authenticated user.
      */
-    private void doAuthenticate(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+    private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
         Authentication authResult = null;
 
-        Object principal = getPreAuthenticatedPrincipal(httpRequest);
-        Object credentials = getPreAuthenticatedCredentials(httpRequest);
+        Object principal = getPreAuthenticatedPrincipal(request);
+        Object credentials = getPreAuthenticatedCredentials(request);
 
         if (principal == null) {
             if (logger.isDebugEnabled()) {
@@ -83,11 +83,11 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends SpringSec
 
         try {
             PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(principal, credentials);
-            authRequest.setDetails(authenticationDetailsSource.buildDetails(httpRequest));
+            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
             authResult = authenticationManager.authenticate(authRequest);
-            successfulAuthentication(httpRequest, httpResponse, authResult);
+            successfulAuthentication(request, response, authResult);
         } catch (AuthenticationException failed) {
-            unsuccessfulAuthentication(httpRequest, httpResponse, failed);
+            unsuccessfulAuthentication(request, response, failed);
         }
     }
 
@@ -144,7 +144,7 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends SpringSec
         this.authenticationManager = authenticationManager;
     }
 
-    protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest);
+    protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
 
-    protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest httpRequest);
+    protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
 }

+ 11 - 0
core/src/main/java/org/springframework/security/ui/preauth/PreAuthenticatedCredentialsNotFoundException.java

@@ -0,0 +1,11 @@
+package org.springframework.security.ui.preauth;
+
+import org.springframework.security.AuthenticationException;
+
+public class PreAuthenticatedCredentialsNotFoundException extends AuthenticationException {
+
+	public PreAuthenticatedCredentialsNotFoundException(String msg) {
+		super(msg);
+	}
+
+}

+ 76 - 0
core/src/main/java/org/springframework/security/ui/preauth/header/RequestHeaderPreAuthenticatedProcessingFilter.java

@@ -0,0 +1,76 @@
+package org.springframework.security.ui.preauth.header;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.ui.FilterChainOrder;
+import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.security.ui.preauth.PreAuthenticatedCredentialsNotFoundException;
+import org.springframework.util.Assert;
+
+/**
+ * A simple pre-authenticated filter which obtains the username from a request header, for use with systems such as
+ * CA Siteminder.
+ * <p>
+ * As with most pre-authenticated scenarios, it is essential that the external authentication system is set up
+ * correctly as this filter does no authentication whatsoever. All the protection is assumed to be provided externally 
+ * and if this filter is included inappropriately in a configuration, it would be possible  to assume the 
+ * identity of a user merely by setting the correct header name. This also means it should not be used in combination
+ * with other Spring Security authentication mechanisms such as form login, as this would imply there was a means of
+ * bypassing the external system which would be risky.
+ * <p>
+ * The property <tt>principalRequestHeader</tt> is the name of the request header that contains the username. It 
+ * defaults to "SM_USER" for compatibility with Siteminder.
+ * 
+ * 
+ * @author Luke Taylor
+ * @version $Id$
+ * @since 2.0
+ */
+public class RequestHeaderPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
+	private String principalRequestHeader = "SM_USER"; 
+	private String credentialsRequestHeader;
+
+	/**
+	 * Read and returns the header named by <tt>principalRequestHeader</tt> from the request.
+	 * 
+	 * @throws PreAuthenticatedCredentialsNotFoundException if the header is missing 
+	 */
+	protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
+		String principal = request.getHeader(principalRequestHeader);
+		
+		if (principal == null) {
+			throw new PreAuthenticatedCredentialsNotFoundException(principalRequestHeader 
+					+ " header not found in request.");
+		}
+
+		return principal;
+	}	
+	
+	/**
+	 * Credentials aren't usually applicable, but if a <tt>credentialsRequestHeader</tt> is set, this
+	 * will be read and used as the credentials value. Otherwise a dummy value will be used. 
+	 */
+	protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
+		if (credentialsRequestHeader != null) {
+			String credentials = request.getHeader(credentialsRequestHeader);
+			
+			return credentials;
+		}
+
+		return "N/A";
+	}
+	
+	public void setPrincipalRequestHeader(String principalRequestHeader) {
+		Assert.hasText(principalRequestHeader, "principalRequestHeader must not be empty or null");
+		this.principalRequestHeader = principalRequestHeader;
+	}
+
+	public void setCredentialsRequestHeader(String credentialsRequestHeader) {
+		Assert.hasText(credentialsRequestHeader, "credentialsRequestHeader must not be empty or null");		
+		this.credentialsRequestHeader = credentialsRequestHeader;
+	}
+
+	public int getOrder() {
+		return FilterChainOrder.PRE_AUTH_FILTER;
+	}
+}

+ 83 - 0
core/src/test/java/org/springframework/security/ui/preauth/header/RequestHeaderPreAuthenticatedProcessingFilterTests.java

@@ -0,0 +1,83 @@
+package org.springframework.security.ui.preauth.header;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.MockAuthenticationManager;
+import org.springframework.security.context.SecurityContextHolder;
+import org.springframework.security.ui.preauth.PreAuthenticatedCredentialsNotFoundException;
+
+/**
+ * 
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class RequestHeaderPreAuthenticatedProcessingFilterTests {
+	
+	@After
+	public void clearContext() {
+		SecurityContextHolder.clearContext();
+	}
+	
+	@Test(expected = PreAuthenticatedCredentialsNotFoundException.class)
+	public void rejectsMissingHeader() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		RequestHeaderPreAuthenticatedProcessingFilter filter = new RequestHeaderPreAuthenticatedProcessingFilter();
+		filter.getOrder();
+		
+		filter.doFilter(request, response, chain);
+	}
+	
+	@Test
+	public void defaultsToUsingSiteminderHeader() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addHeader("SM_USER", "cat");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		RequestHeaderPreAuthenticatedProcessingFilter filter = new RequestHeaderPreAuthenticatedProcessingFilter();
+		filter.setAuthenticationManager(new MockAuthenticationManager());
+		
+		filter.doFilter(request, response, chain);
+		assertNotNull(SecurityContextHolder.getContext().getAuthentication());
+		assertEquals("cat", SecurityContextHolder.getContext().getAuthentication().getName());
+		assertEquals("N/A", SecurityContextHolder.getContext().getAuthentication().getCredentials());		
+	}
+
+	@Test
+	public void alternativeHeaderNameIsSupported() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addHeader("myUsernameHeader", "wolfman");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		RequestHeaderPreAuthenticatedProcessingFilter filter = new RequestHeaderPreAuthenticatedProcessingFilter();
+		filter.setAuthenticationManager(new MockAuthenticationManager());		
+		filter.setPrincipalRequestHeader("myUsernameHeader");
+		
+		filter.doFilter(request, response, chain);
+		assertNotNull(SecurityContextHolder.getContext().getAuthentication());
+		assertEquals("wolfman", SecurityContextHolder.getContext().getAuthentication().getName());
+	}
+	
+	@Test
+	public void credentialsAreRetrievedIfHeaderNameIsSet() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		RequestHeaderPreAuthenticatedProcessingFilter filter = new RequestHeaderPreAuthenticatedProcessingFilter();
+		filter.setAuthenticationManager(new MockAuthenticationManager());		
+		filter.setCredentialsRequestHeader("myCredentialsHeader");
+		request.addHeader("SM_USER", "cat");
+		request.addHeader("myCredentialsHeader", "catspassword");
+		
+		filter.doFilter(request, response, chain);
+		assertNotNull(SecurityContextHolder.getContext().getAuthentication());
+		assertEquals("catspassword", SecurityContextHolder.getContext().getAuthentication().getCredentials());		
+	}	
+	
+}