Skip to content

Commit a8120e7

Browse files
Milan ŠevčíkRob Winch
authored andcommitted
Added authentication filter reading environment variables.
This style is used in many SSO implementations, such as Stanford WebAuth and Shibboleth.
1 parent b443bae commit a8120e7

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.authentication.preauth;
17+
18+
import javax.servlet.http.HttpServletRequest;
19+
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* A simple pre-authenticated filter which obtains the username from an environment variable, for
24+
* use with SSO systems such as Stanford WebAuth or Shibboleth.
25+
* <p>
26+
* As with most pre-authenticated scenarios, it is essential that the external
27+
* authentication system is set up correctly as this filter does no authentication
28+
* whatsoever.
29+
* <p>
30+
* The property {@code principalEnvironmentVariable} is the name of the request environment variable
31+
* that contains the username. It defaults to "REMOTE_USER" for compatibility with WebAuth and Shibboleth.
32+
* <p>
33+
* If the environment variable is missing from the request, {@code getPreAuthenticatedPrincipal} will
34+
* throw an exception. You can override this behaviour by setting the
35+
* {@code exceptionIfVariableMissing} property.
36+
*
37+
*
38+
* @author Milan Sevcik
39+
* @since 4.2
40+
*/
41+
public class EnvironmentVariableAuthenticationFilter extends
42+
AbstractPreAuthenticatedProcessingFilter {
43+
private String principalEnvironmentVariable = "REMOTE_USER";
44+
private String credentialsEnvironmentVariable;
45+
private boolean exceptionIfVariableMissing = true;
46+
47+
/**
48+
* Read and returns the variable named by {@code principalEnvironmentVariable} from the
49+
* request.
50+
*
51+
* @throws PreAuthenticatedCredentialsNotFoundException if the environment variable
52+
* is missing and {@code exceptionIfVariableMissing} is set to {@code true}.
53+
*/
54+
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
55+
String principal = (String)request.getAttribute(principalEnvironmentVariable);
56+
57+
if (principal == null && exceptionIfVariableMissing) {
58+
throw new PreAuthenticatedCredentialsNotFoundException(principalEnvironmentVariable
59+
+ " variable not found in request.");
60+
}
61+
62+
return principal;
63+
}
64+
65+
/**
66+
* Credentials aren't usually applicable, but if a {@code credentialsEnvironmentVariable} is
67+
* set, this will be read and used as the credentials value. Otherwise a dummy value
68+
* will be used.
69+
*/
70+
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
71+
if (credentialsEnvironmentVariable != null) {
72+
return request.getAttribute(credentialsEnvironmentVariable);
73+
}
74+
75+
return "N/A";
76+
}
77+
78+
public void setPrincipalEnvironmentVariable(String principalEnvironmentVariable) {
79+
Assert.hasText(principalEnvironmentVariable,
80+
"principalEnvironmentVariable must not be empty or null");
81+
this.principalEnvironmentVariable = principalEnvironmentVariable;
82+
}
83+
84+
public void setCredentialsEnvironmentVariable(String credentialsEnvironmentVariable) {
85+
Assert.hasText(credentialsEnvironmentVariable,
86+
"credentialsEnvironmentVariable must not be empty or null");
87+
this.credentialsEnvironmentVariable = credentialsEnvironmentVariable;
88+
}
89+
90+
/**
91+
* Defines whether an exception should be raised if the principal variable is missing.
92+
* Defaults to {@code true}.
93+
*
94+
* @param exceptionIfVariableMissing set to {@code false} to override the default
95+
* behaviour and allow the request to proceed if no variable is found.
96+
*/
97+
public void setExceptionIfVariableMissing(boolean exceptionIfVariableMissing) {
98+
this.exceptionIfVariableMissing = exceptionIfVariableMissing;
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.web.authentication.preauth.envvariable;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.Mockito.*;
20+
21+
import org.junit.After;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.mockito.invocation.InvocationOnMock;
25+
import org.mockito.stubbing.Answer;
26+
import org.springframework.mock.web.MockFilterChain;
27+
import org.springframework.mock.web.MockHttpServletRequest;
28+
import org.springframework.mock.web.MockHttpServletResponse;
29+
import org.springframework.security.authentication.AuthenticationManager;
30+
import org.springframework.security.core.Authentication;
31+
import org.springframework.security.core.context.SecurityContextHolder;
32+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
33+
import org.springframework.security.web.authentication.preauth.EnvironmentVariableAuthenticationFilter;
34+
35+
/**
36+
*
37+
* @author Milan Sevcik
38+
*/
39+
public class EnvironmentVariableAuthenticationFilterTests {
40+
41+
@After
42+
@Before
43+
public void clearContext() {
44+
SecurityContextHolder.clearContext();
45+
}
46+
47+
@Test(expected = PreAuthenticatedCredentialsNotFoundException.class)
48+
public void rejectsMissingHeader() throws Exception {
49+
MockHttpServletRequest request = new MockHttpServletRequest();
50+
MockHttpServletResponse response = new MockHttpServletResponse();
51+
MockFilterChain chain = new MockFilterChain();
52+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
53+
54+
filter.doFilter(request, response, chain);
55+
}
56+
57+
@Test
58+
public void defaultsToUsingSiteminderHeader() throws Exception {
59+
MockHttpServletRequest request = new MockHttpServletRequest();
60+
request.setAttribute("REMOTE_USER", "cat");
61+
MockHttpServletResponse response = new MockHttpServletResponse();
62+
MockFilterChain chain = new MockFilterChain();
63+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
64+
filter.setAuthenticationManager(createAuthenticationManager());
65+
66+
filter.doFilter(request, response, chain);
67+
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
68+
assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("cat");
69+
assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("N/A");
70+
}
71+
72+
@Test
73+
public void alternativeHeaderNameIsSupported() throws Exception {
74+
MockHttpServletRequest request = new MockHttpServletRequest();
75+
request.setAttribute("myUsernameVariable", "wolfman");
76+
MockHttpServletResponse response = new MockHttpServletResponse();
77+
MockFilterChain chain = new MockFilterChain();
78+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
79+
filter.setAuthenticationManager(createAuthenticationManager());
80+
filter.setPrincipalEnvironmentVariable("myUsernameVariable");
81+
82+
filter.doFilter(request, response, chain);
83+
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
84+
assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("wolfman");
85+
}
86+
87+
@Test
88+
public void credentialsAreRetrievedIfHeaderNameIsSet() throws Exception {
89+
MockHttpServletRequest request = new MockHttpServletRequest();
90+
MockHttpServletResponse response = new MockHttpServletResponse();
91+
MockFilterChain chain = new MockFilterChain();
92+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
93+
filter.setAuthenticationManager(createAuthenticationManager());
94+
filter.setCredentialsEnvironmentVariable("myCredentialsVariable");
95+
request.setAttribute("REMOTE_USER", "cat");
96+
request.setAttribute("myCredentialsVariable", "catspassword");
97+
98+
filter.doFilter(request, response, chain);
99+
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
100+
assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("catspassword");
101+
}
102+
103+
@Test
104+
public void userIsReauthenticatedIfPrincipalChangesAndCheckForPrincipalChangesIsSet()
105+
throws Exception {
106+
MockHttpServletRequest request = new MockHttpServletRequest();
107+
MockHttpServletResponse response = new MockHttpServletResponse();
108+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
109+
filter.setAuthenticationManager(createAuthenticationManager());
110+
filter.setCheckForPrincipalChanges(true);
111+
request.setAttribute("REMOTE_USER", "cat");
112+
filter.doFilter(request, response, new MockFilterChain());
113+
request = new MockHttpServletRequest();
114+
request.setAttribute("REMOTE_USER", "dog");
115+
filter.doFilter(request, response, new MockFilterChain());
116+
Authentication dog = SecurityContextHolder.getContext().getAuthentication();
117+
assertThat(dog).isNotNull();
118+
assertThat(dog.getName()).isEqualTo("dog");
119+
// Make sure authentication doesn't occur every time (i.e. if the variable *doesn't*
120+
// change)
121+
filter.setAuthenticationManager(mock(AuthenticationManager.class));
122+
filter.doFilter(request, response, new MockFilterChain());
123+
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(dog);
124+
}
125+
126+
@Test(expected = PreAuthenticatedCredentialsNotFoundException.class)
127+
public void missingHeaderCausesException() throws Exception {
128+
MockHttpServletRequest request = new MockHttpServletRequest();
129+
MockHttpServletResponse response = new MockHttpServletResponse();
130+
MockFilterChain chain = new MockFilterChain();
131+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
132+
filter.setAuthenticationManager(createAuthenticationManager());
133+
134+
filter.doFilter(request, response, chain);
135+
}
136+
137+
@Test
138+
public void missingHeaderIsIgnoredIfExceptionIfHeaderMissingIsFalse()
139+
throws Exception {
140+
MockHttpServletRequest request = new MockHttpServletRequest();
141+
MockHttpServletResponse response = new MockHttpServletResponse();
142+
MockFilterChain chain = new MockFilterChain();
143+
EnvironmentVariableAuthenticationFilter filter = new EnvironmentVariableAuthenticationFilter();
144+
filter.setExceptionIfVariableMissing(false);
145+
filter.setAuthenticationManager(createAuthenticationManager());
146+
filter.doFilter(request, response, chain);
147+
}
148+
149+
/**
150+
* Create an authentication manager which returns the passed in object.
151+
*/
152+
private AuthenticationManager createAuthenticationManager() {
153+
AuthenticationManager am = mock(AuthenticationManager.class);
154+
when(am.authenticate(any(Authentication.class))).thenAnswer(
155+
new Answer<Authentication>() {
156+
public Authentication answer(InvocationOnMock invocation)
157+
throws Throwable {
158+
return (Authentication) invocation.getArguments()[0];
159+
}
160+
});
161+
162+
return am;
163+
}
164+
}

0 commit comments

Comments
 (0)