19 changed files with 476 additions and 6 deletions
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* Copyright 2010 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.jaas; |
||||
|
||||
import java.io.IOException; |
||||
import java.security.PrivilegedActionException; |
||||
import java.security.PrivilegedExceptionAction; |
||||
|
||||
import javax.security.auth.Subject; |
||||
import javax.security.auth.login.LoginContext; |
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.ServletRequest; |
||||
import javax.servlet.ServletResponse; |
||||
|
||||
import org.springframework.security.authentication.jaas.JaasAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.web.filter.GenericFilterBean; |
||||
|
||||
/** |
||||
* <p> |
||||
* A <code>Filter</code> which attempts to obtain a JAAS <code>Subject</code> |
||||
* and continue the <code>FilterChain</code> running as that |
||||
* <code>Subject</code>. |
||||
* </p> |
||||
* <p> |
||||
* By using this <code>Filter</code> in conjunction with Spring's |
||||
* <code>JaasAuthenticationProvider</code> both Spring's |
||||
* <code>SecurityContext</code> and a JAAS <code>Subject</code> can be populated |
||||
* simultaneously. This is useful when integrating with code that requires a |
||||
* JAAS <code>Subject</code> to be populated. |
||||
* </p> |
||||
* |
||||
* @author Rob Winch |
||||
* @see #doFilter(ServletRequest, ServletResponse, FilterChain) |
||||
* @see #obtainSubject(ServletRequest) |
||||
*/ |
||||
public class JaasApiIntegrationFilter extends GenericFilterBean { |
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private boolean createEmptySubject; |
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
/** |
||||
* <p> |
||||
* Attempts to obtain and run as a JAAS <code>Subject</code> using |
||||
* {@link #obtainSubject(ServletRequest)}. |
||||
* </p> |
||||
* |
||||
* <p> |
||||
* If the <code>Subject</code> is <code>null</code> and |
||||
* <tt>createEmptySubject</tt> is <code>true</code>, an empty, writeable |
||||
* <code>Subject</code> is used. This allows for the <code>Subject</code> to |
||||
* be populated at the time of login. If the <code>Subject</code> is |
||||
* <code>null</code>, the <code>FilterChain</code> continues with no |
||||
* additional processing. If the <code>Subject</code> is not |
||||
* <code>null</code>, the <code>FilterChain</code> is ran with |
||||
* {@link Subject#doAs(Subject, PrivilegedExceptionAction)} in conjunction |
||||
* with the <code>Subject</code> obtained. |
||||
* </p> |
||||
*/ |
||||
public final void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) |
||||
throws ServletException, IOException { |
||||
|
||||
Subject subject = obtainSubject(request); |
||||
if (subject == null && createEmptySubject) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Subject returned was null and createEmtpySubject is true; creating new empty subject to run as."); |
||||
} |
||||
subject = new Subject(); |
||||
} |
||||
if (subject == null) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Subject is null continue running with no Subject."); |
||||
} |
||||
chain.doFilter(request, response); |
||||
return; |
||||
} |
||||
final PrivilegedExceptionAction<Object> continueChain = new PrivilegedExceptionAction<Object>() { |
||||
public Object run() throws IOException, ServletException { |
||||
chain.doFilter(request, response); |
||||
return null; |
||||
} |
||||
}; |
||||
|
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Running as Subject " + subject); |
||||
} |
||||
try { |
||||
Subject.doAs(subject, continueChain); |
||||
} catch (PrivilegedActionException e) { |
||||
throw new ServletException(e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Obtains the <code>Subject</code> to run as or <code>null</code> if no |
||||
* <code>Subject</code> is available. |
||||
* </p> |
||||
* <p> |
||||
* The default implementation attempts to obtain the <code>Subject</code> |
||||
* from the <code>SecurityContext</code>'s <code>Authentication</code>. If |
||||
* it is of type <code>JaasAuthenticationToken</code> and is authenticated, |
||||
* the <code>Subject</code> is returned from it. Otherwise, |
||||
* <code>null</code> is returned. |
||||
* </p> |
||||
* |
||||
* @param request |
||||
* the current <code>ServletRequest</code> |
||||
* @return the Subject to run as or <code>null</code> if no |
||||
* <code>Subject</code> is available. |
||||
*/ |
||||
protected Subject obtainSubject(ServletRequest request) { |
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Attempting to obtainSubject using authentication : " + authentication); |
||||
} |
||||
if (authentication == null) { |
||||
return null; |
||||
} |
||||
if (!authentication.isAuthenticated()) { |
||||
return null; |
||||
} |
||||
if (!(authentication instanceof JaasAuthenticationToken)) { |
||||
return null; |
||||
} |
||||
JaasAuthenticationToken token = (JaasAuthenticationToken) authentication; |
||||
LoginContext loginContext = token.getLoginContext(); |
||||
if (loginContext == null) { |
||||
return null; |
||||
} |
||||
return loginContext.getSubject(); |
||||
} |
||||
|
||||
/** |
||||
* Sets <code>createEmptySubject</code>. If the value is <code>true</code>, |
||||
* and {@link #obtainSubject(ServletRequest)} returns <code>null</code>, an |
||||
* empty, writeable <code>Subject</code> is created instead. Otherwise no |
||||
* <code>Subject</code> is used. The default is <code>false</code>. |
||||
* |
||||
* @param createEmptySubject |
||||
* the new value |
||||
*/ |
||||
public final void setCreateEmptySubject(boolean createEmptySubject) { |
||||
this.createEmptySubject = createEmptySubject; |
||||
} |
||||
} |
||||
@ -0,0 +1,211 @@
@@ -0,0 +1,211 @@
|
||||
/* |
||||
* Copyright 2010 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.jaas; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertNotNull; |
||||
import static org.junit.Assert.assertNull; |
||||
|
||||
import java.io.IOException; |
||||
import java.security.AccessController; |
||||
import java.security.Principal; |
||||
import java.util.HashMap; |
||||
|
||||
import javax.security.auth.Subject; |
||||
import javax.security.auth.callback.Callback; |
||||
import javax.security.auth.callback.CallbackHandler; |
||||
import javax.security.auth.callback.NameCallback; |
||||
import javax.security.auth.callback.PasswordCallback; |
||||
import javax.security.auth.callback.TextInputCallback; |
||||
import javax.security.auth.callback.UnsupportedCallbackException; |
||||
import javax.security.auth.login.AppConfigurationEntry; |
||||
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; |
||||
import javax.security.auth.login.Configuration; |
||||
import javax.security.auth.login.LoginContext; |
||||
import javax.servlet.ServletRequest; |
||||
import javax.servlet.ServletResponse; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
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.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.authentication.jaas.JaasAuthenticationToken; |
||||
import org.springframework.security.authentication.jaas.TestLoginModule; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.authority.AuthorityUtils; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
|
||||
/** |
||||
* Tests the JaasApiIntegrationFilter. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
public class JaasApiIntegrationFilterTest { |
||||
//~ Instance fields ================================================================================================
|
||||
private JaasApiIntegrationFilter filter; |
||||
private MockHttpServletRequest request; |
||||
private MockHttpServletResponse response; |
||||
private Authentication token; |
||||
private Subject authenticatedSubject; |
||||
private Configuration testConfiguration; |
||||
private CallbackHandler callbackHandler; |
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Before |
||||
public void onBeforeTests() throws Exception { |
||||
this.filter = new JaasApiIntegrationFilter(); |
||||
this.request = new MockHttpServletRequest(); |
||||
this.response = new MockHttpServletResponse(); |
||||
|
||||
authenticatedSubject = new Subject(); |
||||
authenticatedSubject.getPrincipals().add(new Principal() { |
||||
public String getName() { |
||||
return "principal"; |
||||
} |
||||
}); |
||||
authenticatedSubject.getPrivateCredentials().add("password"); |
||||
authenticatedSubject.getPublicCredentials().add("username"); |
||||
callbackHandler = new CallbackHandler() { |
||||
@Override |
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { |
||||
for (Callback callback : callbacks) { |
||||
if (callback instanceof NameCallback) { |
||||
((NameCallback) callback).setName("user"); |
||||
} else if (callback instanceof PasswordCallback) { |
||||
((PasswordCallback) callback).setPassword("password".toCharArray()); |
||||
} else if (callback instanceof TextInputCallback) { |
||||
// ignore
|
||||
} else { |
||||
throw new UnsupportedCallbackException(callback, "Unrecognized Callback " + callback); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
testConfiguration = new Configuration() { |
||||
public void refresh() { |
||||
} |
||||
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) { |
||||
return new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(), |
||||
LoginModuleControlFlag.REQUIRED, new HashMap<String, String>()) }; |
||||
} |
||||
}; |
||||
LoginContext ctx = new LoginContext("SubjectDoAsFilterTest", authenticatedSubject, callbackHandler, |
||||
testConfiguration); |
||||
ctx.login(); |
||||
token = new JaasAuthenticationToken("username", "password", AuthorityUtils.createAuthorityList("ROLE_ADMIN"), |
||||
ctx); |
||||
|
||||
// just in case someone forgot to clear the context
|
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
@After |
||||
public void onAfterTests() { |
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
/** |
||||
* Ensure a Subject was not setup in some other manner. |
||||
*/ |
||||
@Test |
||||
public void currentSubjectNull() { |
||||
assertNull(Subject.getSubject(AccessController.getContext())); |
||||
} |
||||
|
||||
@Test |
||||
public void obtainSubjectNullAuthentication() { |
||||
assertNullSubject(filter.obtainSubject(request)); |
||||
} |
||||
|
||||
@Test |
||||
public void obtainSubjectNonJaasAuthentication() { |
||||
Authentication authentication = new TestingAuthenticationToken("un", "pwd"); |
||||
authentication.setAuthenticated(true); |
||||
SecurityContextHolder.getContext().setAuthentication(authentication); |
||||
assertNullSubject(filter.obtainSubject(request)); |
||||
} |
||||
|
||||
@Test |
||||
public void obtainSubjectNullLoginContext() { |
||||
token = new JaasAuthenticationToken("un", "pwd", AuthorityUtils.createAuthorityList("ROLE_ADMIN"), null); |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
assertNullSubject(filter.obtainSubject(request)); |
||||
} |
||||
|
||||
@Test |
||||
public void obtainSubjectNullSubject() throws Exception { |
||||
LoginContext ctx = new LoginContext("obtainSubjectNullSubject", null, callbackHandler, testConfiguration); |
||||
assertNull(ctx.getSubject()); |
||||
token = new JaasAuthenticationToken("un", "pwd", AuthorityUtils.createAuthorityList("ROLE_ADMIN"), ctx); |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
assertNullSubject(filter.obtainSubject(request)); |
||||
} |
||||
|
||||
@Test |
||||
public void obtainSubject() throws Exception { |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
assertEquals(authenticatedSubject, filter.obtainSubject(request)); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterCurrentSubjectPopulated() throws Exception { |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
assertJaasSubjectEquals(authenticatedSubject); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterAuthenticationNotAuthenticated() throws Exception { |
||||
// Authentication is null, so no Subject is populated.
|
||||
token.setAuthenticated(false); |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
assertJaasSubjectEquals(null); |
||||
filter.setCreateEmptySubject(true); |
||||
assertJaasSubjectEquals(new Subject()); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterAuthenticationNull() throws Exception { |
||||
assertJaasSubjectEquals(null); |
||||
filter.setCreateEmptySubject(true); |
||||
assertJaasSubjectEquals(new Subject()); |
||||
} |
||||
|
||||
//~ Helper Methods ====================================================================================================
|
||||
|
||||
private void assertJaasSubjectEquals(final Subject expectedValue) throws Exception { |
||||
MockFilterChain chain = new MockFilterChain() { |
||||
public void doFilter(ServletRequest request, ServletResponse response) { |
||||
// See if the subject was updated
|
||||
Subject currentSubject = Subject.getSubject(AccessController.getContext()); |
||||
assertEquals(expectedValue, currentSubject); |
||||
|
||||
// run so we know the chain was executed
|
||||
super.doFilter(request, response); |
||||
} |
||||
}; |
||||
filter.doFilter(request, response, chain); |
||||
// ensure that the chain was actually invoked
|
||||
assertNotNull(chain.getRequest()); |
||||
} |
||||
|
||||
private void assertNullSubject(Subject subject) { |
||||
assertNull("Subject is expected to be null, but is not. Got " + subject, subject); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue