SEC-1564: JAAS Configuration can now be injected into DefaultJaasAuthenticationProvider

This commit is contained in:
rwinch 2010-09-10 20:17:22 -05:00
parent 8bf1b8420a
commit 58d9903ebc
19 changed files with 1521 additions and 332 deletions

View File

@ -14,7 +14,8 @@ dependencies {
'javax.annotation:jsr250-api:1.0'
testCompile 'commons-collections:commons-collections:3.2',
"org.springframework:spring-test:$springVersion"
"org.springframework:spring-test:$springVersion",
"ch.qos.logback:logback-classic:$logbackVersion"
testRuntime "hsqldb:hsqldb:$hsqlVersion"
}

View File

@ -0,0 +1,365 @@
/*
* 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.authentication.jaas;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* An {@link AuthenticationProvider} implementation that retrieves user details from a JAAS login configuration.
*
* <p>This <code>AuthenticationProvider</code> is capable of validating {@link
* org.springframework.security.authentication.UsernamePasswordAuthenticationToken} requests contain the correct username and
* password.</p>
* <p>This implementation is backed by a <a
* href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jaas/JAASRefGuide.html">JAAS</a> configuration that is provided by
* a subclass's implementation of {@link #createLoginContext(CallbackHandler)}.
*
* <p>When using JAAS login modules as the authentication source, sometimes the
* <a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/LoginContext.html">LoginContext</a> will
* require <i>CallbackHandler</i>s. The AbstractJaasAuthenticationProvider uses an internal
* <a href="http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/callback/CallbackHandler.html">CallbackHandler
* </a> to wrap the {@link JaasAuthenticationCallbackHandler}s configured in the ApplicationContext.
* When the LoginContext calls the internal CallbackHandler, control is passed to each
* {@link JaasAuthenticationCallbackHandler} for each Callback passed.
* </p>
* <p>{@link JaasAuthenticationCallbackHandler}s are passed to the AbstractJaasAuthenticationProvider through the {@link
* #setCallbackHandlers(org.springframework.security.authentication.jaas.JaasAuthenticationCallbackHandler[]) callbackHandlers}
* property.
* <pre>
* &lt;property name="callbackHandlers"&gt;
* &lt;list&gt;
* &lt;bean class="org.springframework.security.authentication.jaas.TestCallbackHandler"/&gt;
* &lt;bean class="{@link JaasNameCallbackHandler org.springframework.security.authentication.jaas.JaasNameCallbackHandler}"/&gt;
* &lt;bean class="{@link JaasPasswordCallbackHandler org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler}"/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* </pre>
* </p>
* <p>
* After calling LoginContext.login(), the AbstractJaasAuthenticationProvider will retrieve the returned Principals
* from the Subject (LoginContext.getSubject().getPrincipals). Each returned principal is then passed to the
* configured {@link AuthorityGranter}s. An AuthorityGranter is a mapping between a returned Principal, and a role
* name. If an AuthorityGranter wishes to grant an Authorization a role, it returns that role name from it's {@link
* AuthorityGranter#grant(java.security.Principal)} method. The returned role will be applied to the Authorization
* object as a {@link GrantedAuthority}.</p>
* <p>AuthorityGranters are configured in spring xml as follows...
* <pre>
* &lt;property name="authorityGranters"&gt;
* &lt;list&gt;
* &lt;bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* </pre>
*
* @author Ray Krueger
* @author Rob Winch
*/
public abstract class AbstractJaasAuthenticationProvider implements AuthenticationProvider,
ApplicationEventPublisherAware, InitializingBean, ApplicationListener<SessionDestroyedEvent> {
//~ Instance fields ================================================================================================
private ApplicationEventPublisher applicationEventPublisher;
private AuthorityGranter[] authorityGranters;
private JaasAuthenticationCallbackHandler[] callbackHandlers;
protected final Log log = LogFactory.getLog(getClass());
private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
private String loginContextName = "SPRINGSECURITY";
//~ Methods ========================================================================================================
/**
* Validates the required properties are set. In addition, if
* {@link #setCallbackHandlers(JaasAuthenticationCallbackHandler[])} has not
* been called with valid handlers, initializes to use
* {@link JaasNameCallbackHandler} and {@link JaasPasswordCallbackHandler}.
*/
public void afterPropertiesSet() throws Exception {
Assert.hasLength(loginContextName, "loginContextName cannot be null or empty");
Assert.notEmpty(authorityGranters, "authorityGranters cannot be null or empty");
if (ObjectUtils.isEmpty(callbackHandlers)) {
setCallbackHandlers(new JaasAuthenticationCallbackHandler[] { new JaasNameCallbackHandler(),
new JaasPasswordCallbackHandler() });
}
Assert.notNull(loginExceptionResolver, "loginExceptionResolver cannot be null");
}
/**
* Attempts to login the user given the Authentication objects principal and credential
*
* @param auth The Authentication object to be authenticated.
*
* @return The authenticated Authentication object, with it's grantedAuthorities set.
*
* @throws AuthenticationException This implementation does not handle 'locked' or 'disabled' accounts. This method
* only throws a AuthenticationServiceException, with the message of the LoginException that will be
* thrown, should the loginContext.login() method fail.
*/
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (!(auth instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
Set<GrantedAuthority> authorities;
try {
// Create the LoginContext object, and pass our InternallCallbackHandler
LoginContext loginContext = createLoginContext(new InternalCallbackHandler(auth));
// Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
loginContext.login();
// Create a set to hold the authorities, and add any that have already been applied.
authorities = new HashSet<GrantedAuthority>();
authorities.addAll(request.getAuthorities());
// Get the subject principals and pass them to each of the AuthorityGranters
Set<Principal> principals = loginContext.getSubject().getPrincipals();
for (Principal principal : principals) {
for (AuthorityGranter granter : authorityGranters) {
Set<String> roles = granter.grant(principal);
// If the granter doesn't wish to grant any authorities, it should return null.
if ((roles != null) && !roles.isEmpty()) {
for (String role : roles) {
authorities.add(new JaasGrantedAuthority(role, principal));
}
}
}
}
//Convert the authorities set back to an array and apply it to the token.
JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
request.getCredentials(), new ArrayList<GrantedAuthority>(authorities), loginContext);
//Publish the success event
publishSuccessEvent(result);
//we're done, return the token.
return result;
} catch (LoginException loginException) {
AuthenticationException ase = loginExceptionResolver.resolveException(loginException);
publishFailureEvent(request, ase);
throw ase;
}
}
/**
* Creates the LoginContext to be used for authentication.
*
* @param handler The CallbackHandler that should be used for the LoginContext (never <code>null</code>).
* @return the LoginContext to use for authentication.
* @throws LoginException
*/
protected abstract LoginContext createLoginContext(CallbackHandler handler) throws LoginException;
/**
* Handles the logout by getting the SecurityContext for the session that was destroyed. <b>MUST NOT use
* SecurityContextHolder as we are logging out a session that is not related to the current user.</b>
*
* @param event
*/
protected void handleLogout(SessionDestroyedEvent event) {
SecurityContext context = event.getSecurityContext();
if (context == null) {
log.debug("The destroyed session has no SecurityContext");
return;
}
Authentication auth = context.getAuthentication();
if ((auth != null) && (auth instanceof JaasAuthenticationToken)) {
JaasAuthenticationToken token = (JaasAuthenticationToken) auth;
try {
LoginContext loginContext = token.getLoginContext();
boolean debug = log.isDebugEnabled();
if (loginContext != null) {
if (debug) {
log.debug("Logging principal: [" + token.getPrincipal() + "] out of LoginContext");
}
loginContext.logout();
} else if (debug) {
log.debug("Cannot logout principal: [" + token.getPrincipal() + "] from LoginContext. "
+ "The LoginContext is unavailable");
}
} catch (LoginException e) {
log.warn("Error error logging out of LoginContext", e);
}
}
}
public void onApplicationEvent(SessionDestroyedEvent event) {
handleLogout(event);
}
/**
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by subclasses for different
* functionality
*
* @param token The authentication token being processed
* @param ase The excetion that caused the authentication failure
*/
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new JaasAuthenticationFailedEvent(token, ase));
}
}
/**
* Publishes the {@link JaasAuthenticationSuccessEvent}. Can be overridden by subclasses for different
* functionality.
*
* @param token The token being processed
*/
protected void publishSuccessEvent(UsernamePasswordAuthenticationToken token) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new JaasAuthenticationSuccessEvent(token));
}
}
/**
* Returns the AuthorityGrannter array that was passed to the {@link
* #setAuthorityGranters(AuthorityGranter[])} method, or null if it none were ever set.
*
* @return The AuthorityGranter array, or null
*
* @see #setAuthorityGranters(AuthorityGranter[])
*/
AuthorityGranter[] getAuthorityGranters() {
return authorityGranters;
}
/**
* Set the AuthorityGranters that should be consulted for role names to be granted to the Authentication.
*
* @param authorityGranters AuthorityGranter array
*
* @see JaasAuthenticationProvider
*/
public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
this.authorityGranters = authorityGranters;
}
/**
* Returns the current JaasAuthenticationCallbackHandler array, or null if none are set.
*
* @return the JAASAuthenticationCallbackHandlers.
*
* @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
*/
JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
return callbackHandlers;
}
/**
* Set the JAASAuthentcationCallbackHandler array to handle callback objects generated by the
* LoginContext.login method.
*
* @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
*/
public void setCallbackHandlers(JaasAuthenticationCallbackHandler[] callbackHandlers) {
this.callbackHandlers = callbackHandlers;
}
String getLoginContextName() {
return loginContextName;
}
/**
* Set the loginContextName, this name is used as the index to the configuration specified in the
* loginConfig property.
*
* @param loginContextName
*/
public void setLoginContextName(String loginContextName) {
this.loginContextName = loginContextName;
}
LoginExceptionResolver getLoginExceptionResolver() {
return loginExceptionResolver;
}
public void setLoginExceptionResolver(LoginExceptionResolver loginExceptionResolver) {
this.loginExceptionResolver = loginExceptionResolver;
}
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
protected ApplicationEventPublisher getApplicationEventPublisher() {
return applicationEventPublisher;
}
//~ Inner Classes ==================================================================================================
/**
* Wrapper class for JAASAuthenticationCallbackHandlers
*/
private class InternalCallbackHandler implements CallbackHandler {
private final Authentication authentication;
public InternalCallbackHandler(Authentication authentication) {
this.authentication = authentication;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (JaasAuthenticationCallbackHandler handler : callbackHandlers) {
for (Callback callback : callbacks) {
handler.handle(callback, authentication);
}
}
}
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.authentication.jaas;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.springframework.security.authentication.jaas.memory.InMemoryConfiguration;
import org.springframework.util.Assert;
/**
* <p>
* Creates a LoginContext using the Configuration provided to it. This allows
* the configuration to be injected regardless of the value of
* {@link Configuration#getConfiguration()}.
* </p>
* <p>
* While not bound to any particular Configuration implementation, an in memory version of a JAAS
* configuration can be represented using {@link InMemoryConfiguration}.
* </p>
* <p>
* The following JAAS configuration:
* </p>
*
* <pre>
* SPRINGSECURITY {
* sample.SampleLoginModule required;
* };
* </pre>
*
* <p>
* Can be represented as follows:
* </p>
*
* <pre>
* &lt;bean id=&quot;jaasAuthProvider&quot; class=&quot;org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider&quot;&gt;
* &lt;property name=&quot;configuration&quot;&gt;
* &lt;bean class=&quot;org.springframework.security.authentication.jaas.memory.InMemoryConfiguration&quot;&gt;
* &lt;constructor-arg&gt;
* &lt;map&gt;
* &lt;!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider--&gt;
* &lt;entry key=&quot;SPRINGSECURITY&quot;&gt;
* &lt;array&gt;
* &lt;bean class=&quot;javax.security.auth.login.AppConfigurationEntry&quot;&gt;
* &lt;constructor-arg value=&quot;sample.SampleLoginModule&quot; /&gt;
* &lt;constructor-arg&gt;
* &lt;util:constant static-field=&quot;javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED&quot; /&gt;
* &lt;/constructor-arg&gt;
* &lt;constructor-arg&gt;
* &lt;map&gt;&lt;/map&gt;
* &lt;/constructor-arg&gt;
* &lt;/bean&gt;
* &lt;/array&gt;
* &lt;/entry&gt;
* &lt;/map&gt;
* &lt;/constructor-arg&gt;
* &lt;/bean&gt;
* &lt;/property&gt;
* &lt;property name=&quot;authorityGranters&quot;&gt;
* &lt;list&gt;
* &lt;!-- You will need to write your own implementation of AuthorityGranter --&gt;
* &lt;bean class=&quot;org.springframework.security.authentication.jaas.TestAuthorityGranter&quot;/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </pre>
*
* @author Rob Winch
* @see AbstractJaasAuthenticationProvider
* @see InMemoryConfiguration
*/
public class DefaultJaasAuthenticationProvider extends AbstractJaasAuthenticationProvider {
//~ Instance fields ================================================================================================
private Configuration configuration;
//~ Methods ========================================================================================================
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(configuration, "configuration cannot be null.");
}
/**
* Creates a LoginContext using the Configuration that was specified in
* {@link #setConfiguration(Configuration)}.
*/
@Override
protected LoginContext createLoginContext(CallbackHandler handler) throws LoginException {
return new LoginContext(getLoginContextName(), null, handler, getConfiguration());
}
protected Configuration getConfiguration() {
return configuration;
}
/**
* Sets the Configuration to use for Authentication.
*
* @param configuration
* the Configuration that is used when
* {@link #createLoginContext(CallbackHandler)} is called.
*/
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
}

View File

@ -18,35 +18,21 @@ package org.springframework.security.authentication.jaas;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.Principal;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.util.Assert;
@ -125,100 +111,38 @@ import org.springframework.util.Assert;
* </p>
*
* @author Ray Krueger
* @author Rob Winch
*/
public class JaasAuthenticationProvider implements AuthenticationProvider, ApplicationEventPublisherAware,
InitializingBean, ApplicationListener<SessionDestroyedEvent> {
public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvider {
//~ Static fields/initializers =====================================================================================
// exists for passivity
protected static final Log log = LogFactory.getLog(JaasAuthenticationProvider.class);
//~ Instance fields ================================================================================================
private LoginExceptionResolver loginExceptionResolver = new DefaultLoginExceptionResolver();
private Resource loginConfig;
private String loginContextName = "SPRINGSECURITY";
private AuthorityGranter[] authorityGranters;
private JaasAuthenticationCallbackHandler[] callbackHandlers;
private ApplicationEventPublisher applicationEventPublisher;
private boolean refreshConfigurationOnStartup = true;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
// the superclass is not called because it does additional checks that are non-passive
Assert.hasLength(getLoginContextName(), "loginContextName must be set on " + getClass());
Assert.notNull(loginConfig, "loginConfig must be set on " + getClass());
Assert.hasLength(loginContextName, "loginContextName must be set on " + getClass());
configureJaas(loginConfig);
Assert.notNull(Configuration.getConfiguration(),
"As per http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html "
+ "\"If a Configuration object was set via the Configuration.setConfiguration method, then that object is "
+ "returned. Otherwise, a default Configuration object is returned\". Your JRE returned null to "
+ "Configuration.getConfiguration().");
Assert.notNull(
Configuration.getConfiguration(),
"As per http://java.sun.com/j2se/1.5.0/docs/api/javax/security/auth/login/Configuration.html "
+ "\"If a Configuration object was set via the Configuration.setConfiguration method, then that object is "
+ "returned. Otherwise, a default Configuration object is returned\". Your JRE returned null to "
+ "Configuration.getConfiguration().");
}
/**
* Attempts to login the user given the Authentication objects principal and credential
*
* @param auth The Authentication object to be authenticated.
*
* @return The authenticated Authentication object, with it's grantedAuthorities set.
*
* @throws AuthenticationException This implementation does not handle 'locked' or 'disabled' accounts. This method
* only throws a AuthenticationServiceException, with the message of the LoginException that will be
* thrown, should the loginContext.login() method fail.
*/
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (!(auth instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
UsernamePasswordAuthenticationToken request = (UsernamePasswordAuthenticationToken) auth;
Set<GrantedAuthority> authorities;
try {
// Create the LoginContext object, and pass our InternallCallbackHandler
LoginContext loginContext = new LoginContext(loginContextName, new InternalCallbackHandler(auth));
// Attempt to login the user, the LoginContext will call our InternalCallbackHandler at this point.
loginContext.login();
// Create a set to hold the authorities, and add any that have already been applied.
authorities = new HashSet<GrantedAuthority>();
authorities.addAll(request.getAuthorities());
// Get the subject principals and pass them to each of the AuthorityGranters
Set<Principal> principals = loginContext.getSubject().getPrincipals();
for (Principal principal : principals) {
for (AuthorityGranter granter : authorityGranters) {
Set<String> roles = granter.grant(principal);
// If the granter doesn't wish to grant any authorities, it should return null.
if ((roles != null) && !roles.isEmpty()) {
for (String role : roles) {
authorities.add(new JaasGrantedAuthority(role, principal));
}
}
}
}
//Convert the authorities set back to an array and apply it to the token.
JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
request.getCredentials(), new ArrayList<GrantedAuthority>(authorities), loginContext);
//Publish the success event
publishSuccessEvent(result);
//we're done, return the token.
return result;
} catch (LoginException loginException) {
AuthenticationException ase = loginExceptionResolver.resolveException(loginException);
publishFailureEvent(request, ase);
throw ase;
}
@Override
protected LoginContext createLoginContext(CallbackHandler handler) throws LoginException {
return new LoginContext(getLoginContextName(), handler);
}
/**
@ -277,47 +201,7 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
return new URL("file", "", loginConfigPath).toString();
}
/**
* Handles the logout by getting the SecurityContext for the session that was destroyed. <b>MUST NOT use
* SecurityContextHolder as we are logging out a session that is not related to the current user.</b>
*
* @param event
*/
protected void handleLogout(SessionDestroyedEvent event) {
SecurityContext context = event.getSecurityContext();
if (context == null) {
log.debug("The destroyed session has no SecurityContext");
return;
}
Authentication auth = context.getAuthentication();
if ((auth != null) && (auth instanceof JaasAuthenticationToken)) {
JaasAuthenticationToken token = (JaasAuthenticationToken) auth;
try {
LoginContext loginContext = token.getLoginContext();
if (loginContext != null) {
log.debug("Logging principal: [" + token.getPrincipal() + "] out of LoginContext");
loginContext.logout();
} else {
log.debug("Cannot logout principal: [" + token.getPrincipal() + "] from LoginContext. "
+ "The LoginContext is unavailable");
}
} catch (LoginException e) {
log.warn("Error error logging out of LoginContext", e);
}
}
}
public void onApplicationEvent(SessionDestroyedEvent event) {
handleLogout(event);
}
/**
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by subclasses for different
* functionality
@ -326,63 +210,8 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
* @param ase The excetion that caused the authentication failure
*/
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) {
applicationEventPublisher.publishEvent(new JaasAuthenticationFailedEvent(token, ase));
}
/**
* Publishes the {@link JaasAuthenticationSuccessEvent}. Can be overridden by subclasses for different
* functionality.
*
* @param token The token being processed
*/
protected void publishSuccessEvent(UsernamePasswordAuthenticationToken token) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new JaasAuthenticationSuccessEvent(token));
}
}
/**
* Returns the AuthorityGrannter array that was passed to the {@link
* #setAuthorityGranters(AuthorityGranter[])} method, or null if it none were ever set.
*
* @return The AuthorityGranter array, or null
*
* @see #setAuthorityGranters(AuthorityGranter[])
*/
AuthorityGranter[] getAuthorityGranters() {
return authorityGranters;
}
/**
* Set the AuthorityGranters that should be consulted for role names to be granted to the Authentication.
*
* @param authorityGranters AuthorityGranter array
*
* @see JaasAuthenticationProvider
*/
public void setAuthorityGranters(AuthorityGranter[] authorityGranters) {
this.authorityGranters = authorityGranters;
}
/**
* Returns the current JaasAuthenticationCallbackHandler array, or null if none are set.
*
* @return the JAASAuthenticationCallbackHandlers.
*
* @see #setCallbackHandlers(JaasAuthenticationCallbackHandler[])
*/
JaasAuthenticationCallbackHandler[] getCallbackHandlers() {
return callbackHandlers;
}
/**
* Set the JAASAuthentcationCallbackHandler array to handle callback objects generated by the
* LoginContext.login method.
*
* @param callbackHandlers Array of JAASAuthenticationCallbackHandlers
*/
public void setCallbackHandlers(JaasAuthenticationCallbackHandler[] callbackHandlers) {
this.callbackHandlers = callbackHandlers;
// exists for passivity (the superclass does a null check before publishing)
getApplicationEventPublisher().publishEvent(new JaasAuthenticationFailedEvent(token, ase));
}
public Resource getLoginConfig() {
@ -400,28 +229,6 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
this.loginConfig = loginConfig;
}
String getLoginContextName() {
return loginContextName;
}
/**
* Set the loginContextName, this name is used as the index to the configuration specified in the
* loginConfig property.
*
* @param loginContextName
*/
public void setLoginContextName(String loginContextName) {
this.loginContextName = loginContextName;
}
LoginExceptionResolver getLoginExceptionResolver() {
return loginExceptionResolver;
}
public void setLoginExceptionResolver(LoginExceptionResolver loginExceptionResolver) {
this.loginExceptionResolver = loginExceptionResolver;
}
/**
* If set, a call to {@code Configuration#refresh()} will be made by {@code #configureJaas(Resource) }
* method. Defaults to {@code true}.
@ -434,37 +241,4 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
public void setRefreshConfigurationOnStartup(boolean refresh) {
this.refreshConfigurationOnStartup = refresh;
}
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
protected ApplicationEventPublisher getApplicationEventPublisher() {
return applicationEventPublisher;
}
//~ Inner Classes ==================================================================================================
/**
* Wrapper class for JAASAuthenticationCallbackHandlers
*/
private class InternalCallbackHandler implements CallbackHandler {
private final Authentication authentication;
public InternalCallbackHandler(Authentication authentication) {
this.authentication = authentication;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (JaasAuthenticationCallbackHandler handler : callbackHandlers) {
for (Callback callback : callbacks) {
handler.handle(callback, authentication);
}
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.authentication.jaas.memory;
import java.util.Collections;
import java.util.Map;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import org.springframework.util.Assert;
/**
* <p>
* An in memory representation of a JAAS configuration. The constructor accepts
* a Map where the key represents the name of the login context name and the
* value is an Array of {@link AppConfigurationEntry} for that login context
* name. A default Array of {@link AppConfigurationEntry}s can be specified
* which will be returned if a login context is specified which is undefined.
* </p>
*
* @author Rob Winch
*/
public class InMemoryConfiguration extends Configuration {
//~ Instance fields ================================================================================================
private final AppConfigurationEntry[] defaultConfiguration;
private final Map<String, AppConfigurationEntry[]> mappedConfigurations;
//~ Constructors ===================================================================================================
/**
* Creates a new instance with only a defaultConfiguration. Any
* configuration name will result in defaultConfiguration being returned.
*
* @param defaultConfiguration
* The result for any calls to
* {@link #getAppConfigurationEntry(String)}. Can be
* <code>null</code>.
*/
public InMemoryConfiguration(AppConfigurationEntry[] defaultConfiguration) {
this(Collections.<String, AppConfigurationEntry[]> emptyMap(), defaultConfiguration);
}
/**
* Creates a new instance with a mapping of login context name to an array
* of {@link AppConfigurationEntry}s.
*
* @param mappedConfigurations
* each key represents a login context name and each value is an
* Array of {@link AppConfigurationEntry}s that should be used.
*/
public InMemoryConfiguration(Map<String, AppConfigurationEntry[]> mappedConfigurations) {
this(mappedConfigurations, null);
}
/**
* Creates a new instance with a mapping of login context name to an array
* of {@link AppConfigurationEntry}s along with a default configuration that
* will be used if no mapping is found for the given login context name.
*
* @param mappedConfigurations
* each key represents a login context name and each value is an
* Array of {@link AppConfigurationEntry}s that should be used.
* @param defaultConfiguration The result for any calls to
* {@link #getAppConfigurationEntry(String)}. Can be
* <code>null</code>.
*/
public InMemoryConfiguration(Map<String, AppConfigurationEntry[]> mappedConfigurations,
AppConfigurationEntry[] defaultConfiguration) {
Assert.notNull(mappedConfigurations, "mappedConfigurations cannot be null.");
this.mappedConfigurations = mappedConfigurations;
this.defaultConfiguration = defaultConfiguration;
}
//~ Methods ========================================================================================================
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
AppConfigurationEntry[] mappedResult = mappedConfigurations.get(name);
return mappedResult == null ? defaultConfiguration : mappedResult;
}
/**
* Does nothing, but required for JDK5
*/
public void refresh() {
}
}

View File

@ -0,0 +1,4 @@
/**
* An in memory JAAS implementation.
*/
package org.springframework.security.authentication.jaas.memory;

View File

@ -0,0 +1,273 @@
/*
* 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.authentication.jaas;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.Collections;
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.security.auth.login.LoginException;
import org.apache.commons.logging.Log;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent;
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.test.util.ReflectionTestUtils;
public class DefaultJaasAuthenticationProviderTests {
private DefaultJaasAuthenticationProvider provider;
private UsernamePasswordAuthenticationToken token;
private ApplicationEventPublisher publisher;
private Log log;
@Before
public void setUp() throws Exception {
Configuration configuration = mock(Configuration.class);
publisher = mock(ApplicationEventPublisher.class);
log = mock(Log.class);
provider = new DefaultJaasAuthenticationProvider();
provider.setConfiguration(configuration);
provider.setApplicationEventPublisher(publisher);
provider.setAuthorityGranters(new AuthorityGranter[] { new TestAuthorityGranter() });
provider.afterPropertiesSet();
AppConfigurationEntry[] aces = new AppConfigurationEntry[] { new AppConfigurationEntry(
TestLoginModule.class.getName(), LoginModuleControlFlag.REQUIRED,
Collections.<String, Object> emptyMap()) };
when(configuration.getAppConfigurationEntry(provider.getLoginContextName())).thenReturn(aces);
token = new UsernamePasswordAuthenticationToken("user", "password");
ReflectionTestUtils.setField(provider, "log", log);
}
@Test(expected = IllegalArgumentException.class)
public void afterPropertiesSetNullConfiguration() throws Exception {
provider.setConfiguration(null);
provider.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void afterPropertiesSetNullAuthorityGranters() throws Exception {
provider.setAuthorityGranters(null);
provider.afterPropertiesSet();
}
@Test
public void authenticateUnsupportedAuthentication() {
assertEquals(null, provider.authenticate(new TestingAuthenticationToken("user", "password")));
}
@Test
public void authenticateSuccess() throws Exception {
Authentication auth = provider.authenticate(token);
assertEquals(token.getPrincipal(), auth.getPrincipal());
assertEquals(token.getCredentials(), auth.getCredentials());
assertEquals(true, auth.isAuthenticated());
assertEquals(false, auth.getAuthorities().isEmpty());
verify(publisher).publishEvent(isA(JaasAuthenticationSuccessEvent.class));
verifyNoMoreInteractions(publisher);
}
@Test
public void authenticateBadPassword() {
try {
provider.authenticate(new UsernamePasswordAuthenticationToken("user", "asdf"));
fail("LoginException should have been thrown for the bad password");
} catch (AuthenticationException success) {
}
verifyFailedLogin();
}
@Test
public void authenticateBadUser() {
try {
provider.authenticate(new UsernamePasswordAuthenticationToken("asdf", "password"));
fail("LoginException should have been thrown for the bad user");
} catch (AuthenticationException success) {
}
verifyFailedLogin();
}
@Test
public void logout() throws Exception {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
SecurityContext securityContext = mock(SecurityContext.class);
JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
LoginContext context = mock(LoginContext.class);
when(event.getSecurityContext()).thenReturn(securityContext);
when(securityContext.getAuthentication()).thenReturn(token);
when(token.getLoginContext()).thenReturn(context);
provider.onApplicationEvent(event);
verify(event).getSecurityContext();
verify(securityContext).getAuthentication();
verify(token).getLoginContext();
verify(context).logout();
verifyNoMoreInteractions(event, securityContext, token, context);
}
@Test
public void logoutNullSession() {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
provider.handleLogout(event);
verify(event).getSecurityContext();
verify(log).debug(anyString());
verifyNoMoreInteractions(event);
}
@Test
public void logoutNullAuthentication() {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(event.getSecurityContext()).thenReturn(securityContext);
provider.handleLogout(event);
verify(event).getSecurityContext();
verify(event).getSecurityContext();
verify(securityContext).getAuthentication();
verifyNoMoreInteractions(event, securityContext);
}
@Test
public void logoutNonJaasAuthentication() {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(event.getSecurityContext()).thenReturn(securityContext);
when(securityContext.getAuthentication()).thenReturn(token);
provider.handleLogout(event);
verify(event).getSecurityContext();
verify(event).getSecurityContext();
verify(securityContext).getAuthentication();
verifyNoMoreInteractions(event, securityContext);
}
@Test
public void logoutNullLoginContext() throws Exception {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
SecurityContext securityContext = mock(SecurityContext.class);
JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
when(event.getSecurityContext()).thenReturn(securityContext);
when(securityContext.getAuthentication()).thenReturn(token);
provider.onApplicationEvent(event);
verify(event).getSecurityContext();
verify(securityContext).getAuthentication();
verify(token).getLoginContext();
verifyNoMoreInteractions(event, securityContext, token);
}
@Test
public void logoutLoginException() throws Exception {
SessionDestroyedEvent event = mock(SessionDestroyedEvent.class);
SecurityContext securityContext = mock(SecurityContext.class);
JaasAuthenticationToken token = mock(JaasAuthenticationToken.class);
LoginContext context = mock(LoginContext.class);
LoginException loginException = new LoginException("Failed Login");
when(event.getSecurityContext()).thenReturn(securityContext);
when(securityContext.getAuthentication()).thenReturn(token);
when(token.getLoginContext()).thenReturn(context);
doThrow(loginException).when(context).logout();
provider.onApplicationEvent(event);
verify(event).getSecurityContext();
verify(securityContext).getAuthentication();
verify(token).getLoginContext();
verify(context).logout();
verify(log).warn(anyString(), eq(loginException));
verifyNoMoreInteractions(event, securityContext, token, context);
}
@Test
public void publishNullPublisher() {
provider.setApplicationEventPublisher(null);
AuthenticationException ae = new BadCredentialsException("Failed to login", token);
provider.publishFailureEvent(token, ae);
provider.publishSuccessEvent(token);
}
@Test
public void javadocExample() {
String resName = "/" + getClass().getName().replace('.', '/') + ".xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(resName);
context.registerShutdownHook();
try {
provider = context.getBean(DefaultJaasAuthenticationProvider.class);
Authentication auth = provider.authenticate(token);
assertEquals(true, auth.isAuthenticated());
assertEquals(token.getPrincipal(), auth.getPrincipal());
} finally {
context.close();
}
}
private void verifyFailedLogin() {
verify(publisher).publishEvent(argThat(new BaseMatcher<JaasAuthenticationFailedEvent>() {
public void describeTo(Description desc) {
desc.appendText("isA(org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent)");
desc.appendText(" && event.getException() != null");
}
public boolean matches(Object arg) {
JaasAuthenticationFailedEvent e = (JaasAuthenticationFailedEvent) arg;
return e.getException() != null;
}
}));
verifyNoMoreInteractions(publisher);
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.authentication.jaas.memory;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.authentication.jaas.TestLoginModule;
/**
* Tests {@link InMemoryConfiguration}.
*
* @author Rob Winch
*/
public class InMemoryConfigurationTests {
private AppConfigurationEntry[] defaultEntries;
private Map<String, AppConfigurationEntry[]> mappedEntries;
@Before
public void setUp() {
defaultEntries = new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
LoginModuleControlFlag.REQUIRED, Collections.<String, Object> emptyMap()) };
mappedEntries = Collections.<String, AppConfigurationEntry[]> singletonMap("name",
new AppConfigurationEntry[] { new AppConfigurationEntry(TestLoginModule.class.getName(),
LoginModuleControlFlag.OPTIONAL, Collections.<String, Object> emptyMap()) });
}
@Test
public void constructorNullDefault() {
assertNull(new InMemoryConfiguration((AppConfigurationEntry[]) null).getAppConfigurationEntry("name"));
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullMapped() {
new InMemoryConfiguration((Map<String, AppConfigurationEntry[]>) null);
}
@Test
public void constructorEmptyMap() {
assertNull(new InMemoryConfiguration(Collections.<String, AppConfigurationEntry[]> emptyMap())
.getAppConfigurationEntry("name"));
}
@Test
public void constructorEmptyMapNullDefault() {
assertNull(new InMemoryConfiguration(Collections.<String, AppConfigurationEntry[]> emptyMap(), null)
.getAppConfigurationEntry("name"));
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullMapNullDefault() {
new InMemoryConfiguration(null, null);
}
@Test
public void nonnullDefault() {
InMemoryConfiguration configuration = new InMemoryConfiguration(defaultEntries);
assertArrayEquals(defaultEntries, configuration.getAppConfigurationEntry("name"));
}
@Test
public void mappedNonnullDefault() {
InMemoryConfiguration configuration = new InMemoryConfiguration(mappedEntries, defaultEntries);
assertArrayEquals(defaultEntries, configuration.getAppConfigurationEntry("missing"));
assertArrayEquals(mappedEntries.get("name"), configuration.getAppConfigurationEntry("name"));
}
@Test
public void jdk5Compatable() throws Exception {
Method method = InMemoryConfiguration.class.getDeclaredMethod("refresh");
assertEquals(InMemoryConfiguration.class, method.getDeclaringClass());
}
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<bean id="jaasAuthProvider"
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
<property name="configuration">
<bean
class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
<constructor-arg>
<map>
<entry key="SPRINGSECURITY">
<array>
<bean
class="javax.security.auth.login.AppConfigurationEntry">
<constructor-arg
value="org.springframework.security.authentication.jaas.TestLoginModule" />
<constructor-arg>
<util:constant
static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
</constructor-arg>
<constructor-arg>
<map></map>
</constructor-arg>
</bean>
</array>
</entry>
</map>
</constructor-arg>
</bean>
</property>
<property name="authorityGranters">
<list>
<bean
class="org.springframework.security.authentication.jaas.TestAuthorityGranter" />
</list>
</property>
</bean>
</beans>

View File

@ -1,28 +1,188 @@
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="jaas">
<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="jaas">
<info>
<title>Java Authentication and Authorization Service (JAAS) Provider</title>
</info>
<section xml:id="jaas-overview">
<section xml:aid="jaas-overview">
<info>
<title>Overview</title>
</info>
<para>Spring Security provides a package able to delegate authentication requests to the
Java Authentication and Authorization Service (JAAS). This package is discussed in
detail below.</para>
</section>
<section xml:id="jaas-abstractjaasauthenticationprovider">
<info>
<title>AbstractJaasAuthenticationProvider</title>
</info>
<para>The <classname>AbstractJaasAuthenticationProvider</classname> is the basis for the
provided JAAS <interfacename>AuthenticationProvider</interfacename> implementations. Subclasses
must implement a method that creates the <classname>LoginContext</classname>. The
<classname>AbstractJaasAuthenticationProvider</classname> has a number of dependencies that can
be injected into it that are discussed below.</para>
<para>Central to JAAS operation are login configuration files. To learn more about JAAS
login configuration files, consult the JAAS reference documentation available from Sun
Microsystems. We expect you to have a basic understanding of JAAS and its login
configuration file syntax in order to understand this section.</para>
<section xml:id="jaas-callbackhandler">
<info>
<title xml:id="jaas-callback-handler">JAAS CallbackHandler</title>
</info>
<para>Most JAAS <literal>LoginModule</literal>s require a callback of some sort. These
callbacks are usually used to obtain the username and password from the user.</para>
<para>In a Spring Security deployment, Spring Security is responsible for this user
interaction (via the authentication mechanism). Thus, by the time the authentication
request is delegated through to JAAS, Spring Security's authentication mechanism
will already have fully-populated an <interfacename>Authentication</interfacename>
object containing all the information required by the JAAS
<literal>LoginModule</literal>.</para>
<para>Therefore, the JAAS package for Spring Security provides two default callback
handlers, <literal>JaasNameCallbackHandler</literal> and
<literal>JaasPasswordCallbackHandler</literal>. Each of these callback handlers
implement <literal>JaasAuthenticationCallbackHandler</literal>. In most cases these
callback handlers can simply be used without understanding the internal
mechanics.</para>
<para>For those needing full control over the callback behavior, internally
<classname>AbstractJaasAuthenticationProvider</classname> wraps these
<literal>JaasAuthenticationCallbackHandler</literal>s with an
<literal>InternalCallbackHandler</literal>. The
<literal>InternalCallbackHandler</literal> is the class that actually implements
JAAS normal <literal>CallbackHandler</literal> interface. Any time that the JAAS
<literal>LoginModule</literal> is used, it is passed a list of application context
configured <literal>InternalCallbackHandler</literal>s. If the
<literal>LoginModule</literal> requests a callback against the
<literal>InternalCallbackHandler</literal>s, the callback is in-turn passed to the
<literal>JaasAuthenticationCallbackHandler</literal>s being wrapped.</para>
</section>
<section xml:id="jaas-authoritygranter">
<info>
<title xml:id="jaas-authority-granter">JAAS AuthorityGranter</title>
</info>
<para>JAAS works with principals. Even "roles" are represented as principals in JAAS.
Spring Security, on the other hand, works with
<interfacename>Authentication</interfacename> objects. Each
<interfacename>Authentication</interfacename> object contains a single principal,
and multiple <interfacename>GrantedAuthority</interfacename>s. To facilitate
mapping between these different concepts, Spring Security's JAAS package includes an
<literal>AuthorityGranter</literal> interface.</para>
<para>An <literal>AuthorityGranter</literal> is responsible for inspecting a JAAS
principal and returning a set of <literal>String</literal>s, representing the
authorities assigned to the principal. For each returned authority string, the
<classname>AbstractJaasAuthenticationProvider</classname> creates a
<classname>JaasGrantedAuthority</classname> (which implements Spring Securitys
<interfacename>GrantedAuthority</interfacename> interface) containing the authority
string and the JAAS principal that the
<interfacename>AuthorityGranter</interfacename> was passed. The
<classname>AbstractJaasAuthenticationProvider</classname> obtains the JAAS principals by
firstly successfully authenticating the users credentials using the JAAS
<literal>LoginModule</literal>, and then accessing the
<literal>LoginContext</literal> it returns. A call to
<literal>LoginContext.getSubject().getPrincipals()</literal> is made, with each
resulting principal passed to each <interfacename>AuthorityGranter</interfacename>
defined against the
<literal>AbstractJaasAuthenticationProvider.setAuthorityGranters(List)</literal>
property.</para>
<para>Spring Security does not include any production
<interfacename>AuthorityGranter</interfacename>s given that every JAAS principal has
an implementation-specific meaning. However, there is a
<literal>TestAuthorityGranter</literal> in the unit tests that demonstrates a simple
<literal>AuthorityGranter</literal> implementation.</para>
</section>
</section>
<section xml:id="jaas-defaultjaasauthenticationprovider">
<info>
<title>DefaultJaasAuthenticationProvider</title>
</info>
<para>The <classname>DefaultJaasAuthenticationProvider</classname> allows a JAAS
<classname>Configuration</classname> object to be injected into it as a dependency. It then
creates a <classname>LoginContext</classname> using the injected JAAS <classname>Configuration</classname>.
This means that <classname>DefaultJaasAuthenticationProvider</classname> is not bound any particular implementation
of <classname>Configuration</classname> as <classname>JaasAuthenticationProvider</classname> is.</para>
<section xml:id="jaas-inmemoryconfiguration">
<info>
<title>InMemoryConfiguration</title>
</info>
<para>In order to make it easy to inject a <classname>Configuration</classname> into
<classname>DefaultJaasAuthenticationProvider</classname>, a default in memory
implementation named <classname>InMemoryConfiguration</classname> is provided. The
implementation constructor accepts a <interfacename>Map</interfacename> where each key represents a
login configuration name and the value represents an <classname>Array</classname> of
<classname>AppConfigurationEntry</classname>s.
<classname>InMemoryConfiguration</classname> also supports a default
<classname>Array</classname> of <classname>AppConfigurationEntry</classname> objects that
will be used if no mapping is found within the provided <interfacename>Map</interfacename>. For
details, refer to the class level javadoc of <classname>InMemoryConfiguration</classname>.</para>
</section>
<section xml:id="jaas-djap-config">
<info>
<title>DefaultJaasAuthenticationProvider Example Configuration</title>
</info>
<para>While the Spring configuration for <classname>InMemoryConfiguration</classname> can be
more verbose than the standarad JAAS configuration files, using it in conjuction with
<classname>DefaultJaasAuthenticationProvider</classname> is more flexible than
<classname>JaasAuthenticationProvider</classname> since it not dependant on the default
<classname>Configuration</classname> implementation.</para>
<para>An example configuration of <classname>DefaultJaasAuthenticationProvider</classname> using
<classname>InMemoryConfiguration</classname> is provided below. Note that custom implementations of
<classname>Configuration</classname> can easily be injected into
<classname>DefaultJaasAuthenticationProvider</classname> as well.</para>
<programlisting><![CDATA[
<bean id="jaasAuthProvider"
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
<property name="configuration">
<bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
<constructor-arg>
<map>
<!--
SPRINGSECURITY is the default loginContextName
for AbstractJaasAuthenticationProvider
-->
<entry key="SPRINGSECURITY">
<array>
<bean class="javax.security.auth.login.AppConfigurationEntry">
<constructor-arg value="sample.SampleLoginModule" />
<constructor-arg>
<util:constant static-field=
"javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/>
</constructor-arg>
<constructor-arg>
<map></map>
</constructor-arg>
</bean>
</array>
</entry>
</map>
</constructor-arg>
</bean>
</property>
<property name="authorityGranters">
<list>
<!-- You will need to write your own implementation of AuthorityGranter -->
<bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
</list>
</property>
</bean>
]]></programlisting>
</section>
</section>
<section xml:id="jaas-config">
<section xml:id="jaas-jaasauthenticationprovider">
<info>
<title>Configuration</title>
<title>JaasAuthenticationProvider</title>
</info>
<para>The <literal>JaasAuthenticationProvider</literal> attempts to authenticate a users
principal and credentials through JAAS.</para>
<para>The <classname>JaasAuthenticationProvider</classname> assumes the default <classname>Configuration</classname> is an instance of
<link xlink:href="http://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/spec/com/sun/security/auth/login/ConfigFile.html">
ConfigFile</link>. This assumption is made in order to attempt to update the <classname>Configuration</classname>. The
<classname>JaasAuthenticationProvider</classname> then uses the default <classname>Configuration</classname> to create the
<classname>LoginContext</classname>.</para>
<para>Lets assume we have a JAAS login configuration file,
<literal>/WEB-INF/login.conf</literal>, with the following contents:
@ -52,81 +212,5 @@ JAASTest {
</property>
</bean>
]]></programlisting></para>
<para>The <literal>CallbackHandler</literal>s and
<interfacename>AuthorityGranter</interfacename>s are discussed below.</para>
<section xml:id="jaas-callbackhandler">
<info>
<title xml:id="jaas-callback-handler">JAAS CallbackHandler</title>
</info>
<para>Most JAAS <literal>LoginModule</literal>s require a callback of some sort. These
callbacks are usually used to obtain the username and password from the user.</para>
<para>In a Spring Security deployment, Spring Security is responsible for this user
interaction (via the authentication mechanism). Thus, by the time the authentication
request is delegated through to JAAS, Spring Security's authentication mechanism
will already have fully-populated an <interfacename>Authentication</interfacename>
object containing all the information required by the JAAS
<literal>LoginModule</literal>.</para>
<para>Therefore, the JAAS package for Spring Security provides two default callback
handlers, <literal>JaasNameCallbackHandler</literal> and
<literal>JaasPasswordCallbackHandler</literal>. Each of these callback handlers
implement <literal>JaasAuthenticationCallbackHandler</literal>. In most cases these
callback handlers can simply be used without understanding the internal
mechanics.</para>
<para>For those needing full control over the callback behavior, internally
<literal>JaasAuthenticationProvider</literal> wraps these
<literal>JaasAuthenticationCallbackHandler</literal>s with an
<literal>InternalCallbackHandler</literal>. The
<literal>InternalCallbackHandler</literal> is the class that actually implements
JAAS normal <literal>CallbackHandler</literal> interface. Any time that the JAAS
<literal>LoginModule</literal> is used, it is passed a list of application context
configured <literal>InternalCallbackHandler</literal>s. If the
<literal>LoginModule</literal> requests a callback against the
<literal>InternalCallbackHandler</literal>s, the callback is in-turn passed to the
<literal>JaasAuthenticationCallbackHandler</literal>s being wrapped.</para>
</section>
<section xml:id="jaas-authoritygranter">
<info>
<title xml:id="jaas-authority-granter">JAAS AuthorityGranter</title>
</info>
<para>JAAS works with principals. Even "roles" are represented as principals in JAAS.
Spring Security, on the other hand, works with
<interfacename>Authentication</interfacename> objects. Each
<interfacename>Authentication</interfacename> object contains a single principal,
and multiple <interfacename>GrantedAuthority</interfacename>s. To facilitate
mapping between these different concepts, Spring Security's JAAS package includes an
<literal>AuthorityGranter</literal> interface.</para>
<para>An <literal>AuthorityGranter</literal> is responsible for inspecting a JAAS
principal and returning a set of <literal>String</literal>s, representing the
authorities assigned to the principal. For each returned authority string, the
<classname>JaasAuthenticationProvider</classname> creates a
<classname>JaasGrantedAuthority</classname> (which implements Spring Securitys
<interfacename>GrantedAuthority</interfacename> interface) containing the authority
string and the JAAS principal that the
<interfacename>AuthorityGranter</interfacename> was passed. The
<classname>JaasAuthenticationProvider</classname> obtains the JAAS principals by
firstly successfully authenticating the users credentials using the JAAS
<literal>LoginModule</literal>, and then accessing the
<literal>LoginContext</literal> it returns. A call to
<literal>LoginContext.getSubject().getPrincipals()</literal> is made, with each
resulting principal passed to each <interfacename>AuthorityGranter</interfacename>
defined against the
<literal>JaasAuthenticationProvider.setAuthorityGranters(List)</literal>
property.</para>
<para>Spring Security does not include any production
<interfacename>AuthorityGranter</interfacename>s given that every JAAS principal has
an implementation-specific meaning. However, there is a
<literal>TestAuthorityGranter</literal> in the unit tests that demonstrates a simple
<literal>AuthorityGranter</literal> implementation.</para>
</section>
</section>
</chapter>
</chapter>

View File

@ -129,6 +129,12 @@ Success! Your web filters appear to be properly configured!
download the CAS Server web application (a war file) from the CAS site and drop it into
the <filename>samples/cas/server</filename> directory. </para>
</section>
<section xml:id="jaas-sample">
<title>JAAS Sample</title>
<para>The JAAS sample is very simple example of how to use a JAAS LoginModule with Spring Security. The provided LoginModule will
successfully authenticate a user if the username equals the password otherwise a LoginException is thrown. The AuthorityGranter
used in this example always grants the role ROLE_USER.</para>
</section>
<section xml:id="preauth-sample">
<title>Pre-Authentication Sample</title>
<para> This sample application demonstrates how to wire up beans from the <link

25
samples/jaas/jaas.gradle Normal file
View File

@ -0,0 +1,25 @@
// JAAS sample build file
apply plugin: 'war'
apply plugin: 'jetty'
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5@jar'
compile project(':spring-security-core'),
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-web:$springVersion"
runtime project(':spring-security-web'),
project(':spring-security-config'),
project(':spring-security-taglibs'),
"org.springframework:spring-context-support:$springVersion",
"javax.servlet:jstl:$jstlVersion",
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"ch.qos.logback:logback-classic:$logbackVersion"
}
jettyRun {
contextPath = "/jaas"
}

View File

@ -0,0 +1,34 @@
/*
* 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 samples.jaas;
import java.security.Principal;
import java.util.Collections;
import java.util.Set;
import org.springframework.security.authentication.jaas.AuthorityGranter;
/**
* An AuthorityGranter that always grants "ROLE_USER".
*
* @author Rob Winch
*/
public class RoleUserAuthorityGranter implements AuthorityGranter {
public Set<String> grant(Principal principal) {
return Collections.singleton("ROLE_USER");
}
}

View File

@ -0,0 +1,85 @@
/*
* 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 samples.jaas;
import java.security.Principal;
import java.util.Map;
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.login.LoginException;
import javax.security.auth.spi.LoginModule;
/**
* A LoginModule that will allow login if the username equals the password. Upon
* successful authentication it adds the username as a Principal.
*
* @author Rob Winch
*/
public class UsernameEqualsPasswordLoginModule implements LoginModule {
//~ Instance fields ================================================================================================
private String password;
private String username;
private Subject subject;
//~ Methods ========================================================================================================
public boolean abort() throws LoginException {
return true;
}
public boolean commit() throws LoginException {
return true;
}
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
try {
NameCallback nameCallback = new NameCallback("prompt");
PasswordCallback passwordCallback = new PasswordCallback("prompt", false);
callbackHandler.handle(new Callback[] { nameCallback, passwordCallback });
password = new String(passwordCallback.getPassword());
username = nameCallback.getName();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean login() throws LoginException {
if (username != null && !username.equals(password)) {
throw new LoginException("username is not equal to password");
}
subject.getPrincipals().add(new Principal() {
public String getName() {
return username;
}
});
return true;
}
public boolean logout() throws LoginException {
return true;
}
}

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<sec:http auto-config="true" use-expressions="true">
<sec:intercept-url pattern="/secure/**" access="isAuthenticated()"/>
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider ref="jaasAuthProvider"/>
</sec:authentication-manager>
<bean id="jaasAuthProvider"
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
<property name="configuration">
<bean
class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
<constructor-arg>
<map>
<entry key="SPRINGSECURITY">
<array>
<bean class="javax.security.auth.login.AppConfigurationEntry">
<constructor-arg
value="samples.jaas.UsernameEqualsPasswordLoginModule" />
<constructor-arg>
<util:constant
static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
</constructor-arg>
<constructor-arg>
<map></map>
</constructor-arg>
</bean>
</array>
</entry>
</map>
</constructor-arg>
</bean>
</property>
<property name="authorityGranters">
<list>
<bean class="samples.jaas.RoleUserAuthorityGranter" />
</list>
</property>
</bean>
</beans>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- JAAS web application
-
-->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>JAAS Sample Application</display-name>
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-security.xml
</param-value>
</context-param>
<!-- Nothing below here needs to be modified -->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>jaas.root</param-value>
</context-param>
<filter>
<filter-name>localizationFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>localizationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
- Loads the root application context of this web app at startup.
- The application context is then available via
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -0,0 +1,18 @@
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<html>
<body>
<h1>Home Page</h1>
<p>
Anyone can view this page.
</p>
<p>
Your principal object is....: <%= request.getUserPrincipal() %>
</p>
<p>
<sec:authorize url='/secure/index.jsp'>You can currently access "/secure" URLs.</sec:authorize>
</p>
<p>
<a href="secure/index.jsp">Secure page</a></p>
</body>
</html>

View File

@ -0,0 +1,40 @@
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import="org.springframework.security.core.Authentication" %>
<%@ page import="org.springframework.security.core.GrantedAuthority" %>
<html>
<head>
<title>Security Debug Information</title>
</head>
<body>
<h3>Security Debug Information</h3>
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
<p>
Authentication object is of type: <em><%= auth.getClass().getName() %></em>
</p>
<p>
Authentication object as a String: <br/><br/><%= auth.toString() %>
</p>
Authentication object holds the following granted authorities:<br /><br />
<%
for (GrantedAuthority authority : auth.getAuthorities()) { %>
<%= authority %> (<em>getAuthority()</em>: <%= authority.getAuthority() %>)<br />
<% }
%>
<p><b>Success! Your web filters appear to be properly configured!</b></p>
<%
} else {
%>
Authentication object is null.<br />
This is an error and your Spring Security application will not operate properly until corrected.<br /><br />
<% }
%>
</body>
</html>

View File

@ -19,7 +19,8 @@ def String[] samples = [
'dms',
'preauth',
'cas',
'ldap'
'ldap',
'jaas'
]
def String[] itest = [