Przeglądaj źródła

Added authentication filter reading environment variables.

This style is used in many SSO implementations, such as Stanford WebAuth
and Shibboleth.
Milan Ševčík 9 lat temu
rodzic
commit
a8120e74a7

+ 100 - 0
web/src/main/java/org/springframework/security/web/authentication/preauth/EnvironmentVariableAuthenticationFilter.java

@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.authentication.preauth;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.Assert;
+
+/**
+ * A simple pre-authenticated filter which obtains the username from an environment variable, for
+ * use with SSO systems such as Stanford WebAuth or Shibboleth.
+ * <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.
+ * <p>
+ * The property {@code principalEnvironmentVariable} is the name of the request environment variable
+ * that contains the username. It defaults to "REMOTE_USER" for compatibility with WebAuth and Shibboleth.
+ * <p>
+ * If the environment variable is missing from the request, {@code getPreAuthenticatedPrincipal} will
+ * throw an exception. You can override this behaviour by setting the
+ * {@code exceptionIfVariableMissing} property.
+ *
+ *
+ * @author Milan Sevcik
+ * @since 4.2
+ */
+public class EnvironmentVariableAuthenticationFilter extends
+	AbstractPreAuthenticatedProcessingFilter {
+	private String principalEnvironmentVariable = "REMOTE_USER";
+	private String credentialsEnvironmentVariable;
+	private boolean exceptionIfVariableMissing = true;
+
+	/**
+	 * Read and returns the variable named by {@code principalEnvironmentVariable} from the
+	 * request.
+	 *
+	 * @throws PreAuthenticatedCredentialsNotFoundException if the environment variable
+	 * is missing and {@code exceptionIfVariableMissing} is set to {@code true}.
+	 */
+	protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
+		String principal = (String)request.getAttribute(principalEnvironmentVariable);
+
+		if (principal == null && exceptionIfVariableMissing) {
+			throw new PreAuthenticatedCredentialsNotFoundException(principalEnvironmentVariable
+				+ " variable not found in request.");
+		}
+
+		return principal;
+	}
+
+	/**
+	 * Credentials aren't usually applicable, but if a {@code credentialsEnvironmentVariable} 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 (credentialsEnvironmentVariable != null) {
+			return request.getAttribute(credentialsEnvironmentVariable);
+		}
+
+		return "N/A";
+	}
+
+	public void setPrincipalEnvironmentVariable(String principalEnvironmentVariable) {
+		Assert.hasText(principalEnvironmentVariable,
+			"principalEnvironmentVariable must not be empty or null");
+		this.principalEnvironmentVariable = principalEnvironmentVariable;
+	}
+
+	public void setCredentialsEnvironmentVariable(String credentialsEnvironmentVariable) {
+		Assert.hasText(credentialsEnvironmentVariable,
+			"credentialsEnvironmentVariable must not be empty or null");
+		this.credentialsEnvironmentVariable = credentialsEnvironmentVariable;
+	}
+
+	/**
+	 * Defines whether an exception should be raised if the principal variable is missing.
+	 * Defaults to {@code true}.
+	 *
+	 * @param exceptionIfVariableMissing set to {@code false} to override the default
+	 * behaviour and allow the request to proceed if no variable is found.
+	 */
+	public void setExceptionIfVariableMissing(boolean exceptionIfVariableMissing) {
+		this.exceptionIfVariableMissing = exceptionIfVariableMissing;
+	}
+}

+ 164 - 0
web/src/test/java/org/springframework/security/web/authentication/preauth/envvariable/EnvironmentVariableAuthenticationFilterTests.java

@@ -0,0 +1,164 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.authentication.preauth.envvariable;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
+import org.springframework.security.web.authentication.preauth.EnvironmentVariableAuthenticationFilter;
+
+/**
+ *
+ * @author Milan Sevcik
+ */
+public class EnvironmentVariableAuthenticationFilterTests {
+
+	@After
+	@Before
+	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();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+
+		filter.doFilter(request, response, chain);
+	}
+
+	@Test
+	public void defaultsToUsingSiteminderHeader() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.setAttribute("REMOTE_USER", "cat");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setAuthenticationManager(createAuthenticationManager());
+
+		filter.doFilter(request, response, chain);
+		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
+		assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("cat");
+		assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("N/A");
+	}
+
+	@Test
+	public void alternativeHeaderNameIsSupported() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.setAttribute("myUsernameVariable", "wolfman");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setAuthenticationManager(createAuthenticationManager());
+		filter.setPrincipalEnvironmentVariable("myUsernameVariable");
+
+		filter.doFilter(request, response, chain);
+		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
+		assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("wolfman");
+	}
+
+	@Test
+	public void credentialsAreRetrievedIfHeaderNameIsSet() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setAuthenticationManager(createAuthenticationManager());
+		filter.setCredentialsEnvironmentVariable("myCredentialsVariable");
+		request.setAttribute("REMOTE_USER", "cat");
+		request.setAttribute("myCredentialsVariable", "catspassword");
+
+		filter.doFilter(request, response, chain);
+		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
+		assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("catspassword");
+	}
+
+	@Test
+	public void userIsReauthenticatedIfPrincipalChangesAndCheckForPrincipalChangesIsSet()
+			throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setAuthenticationManager(createAuthenticationManager());
+		filter.setCheckForPrincipalChanges(true);
+		request.setAttribute("REMOTE_USER", "cat");
+		filter.doFilter(request, response, new MockFilterChain());
+		request = new MockHttpServletRequest();
+		request.setAttribute("REMOTE_USER", "dog");
+		filter.doFilter(request, response, new MockFilterChain());
+		Authentication dog = SecurityContextHolder.getContext().getAuthentication();
+		assertThat(dog).isNotNull();
+		assertThat(dog.getName()).isEqualTo("dog");
+		// Make sure authentication doesn't occur every time (i.e. if the variable *doesn't*
+		// change)
+		filter.setAuthenticationManager(mock(AuthenticationManager.class));
+		filter.doFilter(request, response, new MockFilterChain());
+		assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(dog);
+	}
+
+	@Test(expected = PreAuthenticatedCredentialsNotFoundException.class)
+	public void missingHeaderCausesException() throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setAuthenticationManager(createAuthenticationManager());
+
+		filter.doFilter(request, response, chain);
+	}
+
+	@Test
+	public void missingHeaderIsIgnoredIfExceptionIfHeaderMissingIsFalse()
+			throws Exception {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		MockFilterChain chain = new MockFilterChain();
+		EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
+		filter.setExceptionIfVariableMissing(false);
+		filter.setAuthenticationManager(createAuthenticationManager());
+		filter.doFilter(request, response, chain);
+	}
+
+	/**
+	 * Create an authentication manager which returns the passed in object.
+	 */
+	private AuthenticationManager createAuthenticationManager() {
+		AuthenticationManager am = mock(AuthenticationManager.class);
+		when(am.authenticate(any(Authentication.class))).thenAnswer(
+				new Answer<Authentication>() {
+					public Authentication answer(InvocationOnMock invocation)
+							throws Throwable {
+						return (Authentication) invocation.getArguments()[0];
+					}
+				});
+
+		return am;
+	}
+}