Added authentication filter reading environment variables.

This style is used in many SSO implementations, such as Stanford WebAuth
and Shibboleth.
This commit is contained in:
Milan Ševčík 2016-07-15 14:41:13 +02:00 committed by Rob Winch
parent b443baef04
commit a8120e74a7
2 changed files with 264 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}