SEC-1536: added JAAS API Integration, updated doc, updated jaas sample
This commit is contained in:
+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;
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user