diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/EnvironmentVariableAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/EnvironmentVariableAuthenticationFilter.java new file mode 100644 index 0000000000..c717f23ceb --- /dev/null +++ b/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. + *
+ * 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. + *
+ * 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. + *
+ * 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;
+ }
+}
diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/envvariable/EnvironmentVariableAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/envvariable/EnvironmentVariableAuthenticationFilterTests.java
new file mode 100644
index 0000000000..40afd9ac6c
--- /dev/null
+++ b/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