1
0
mirror of synced 2026-05-22 21:33:16 +00:00

SEC-1536: added JAAS API Integration, updated doc, updated jaas sample

This commit is contained in:
rwinch
2010-09-13 13:12:45 -05:00
parent 0217e98bdb
commit de819378fc
19 changed files with 476 additions and 6 deletions
@@ -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 @@
/*
* 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);
}
}
+2 -1
View File
@@ -13,7 +13,8 @@ dependencies {
provided 'javax.servlet:servlet-api:2.5'
testCompile 'commons-codec:commons-codec:1.3',
testCompile project(':spring-security-core').sourceSets.test.classes,
'commons-codec:commons-codec:1.3',
"org.springframework:spring-test:$springVersion"
testRuntime "hsqldb:hsqldb:$hsqlVersion"
}