SEC-1078: Converted WASSecurityHelper to an internal interface and added test for scenario from this issue.

This commit is contained in:
Luke Taylor 2009-04-27 06:01:40 +00:00
parent e45f6914ee
commit 6bd7421a1b
6 changed files with 136 additions and 248 deletions

View File

@ -1,206 +0,0 @@
package org.springframework.security.web.authentication.preauth.websphere;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.security.auth.Subject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* WebSphere Security helper class to allow retrieval of the current username and groups.
* <p>
* See Spring Security Jira SEC-477.
*
* @author Ruud Senden
* @author Stephane Manciot
* @since 2.0
*/
final class WASSecurityHelper {
private static final Log logger = LogFactory.getLog(WASSecurityHelper.class);
private static final String USER_REGISTRY = "UserRegistry";
private static Method getRunAsSubject = null;
private static Method getGroupsForUser = null;
private static Method getSecurityName = null;
// SEC-803
private static Class<?> wsCredentialClass = null;
/**
* Get the security name for the given subject.
*
* @param subject
* The subject for which to retrieve the security name
* @return String the security name for the given subject
*/
private static final String getSecurityName(final Subject subject) {
if (logger.isDebugEnabled()) {
logger.debug("Determining Websphere security name for subject " + subject);
}
String userSecurityName = null;
if (subject != null) {
// SEC-803
Object credential = subject.getPublicCredentials(getWSCredentialClass()).iterator().next();
if (credential != null) {
userSecurityName = (String)invokeMethod(getSecurityNameMethod(),credential,null);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Websphere security name is " + userSecurityName + " for subject " + subject);
}
return userSecurityName;
}
/**
* Get the current RunAs subject.
*
* @return Subject the current RunAs subject
*/
private static final Subject getRunAsSubject() {
logger.debug("Retrieving WebSphere RunAs subject");
// get Subject: WSSubject.getCallerSubject ();
return (Subject) invokeMethod(getRunAsSubjectMethod(), null, new Object[] {});
}
/**
* Get the WebSphere group names for the given subject.
*
* @param subject
* The subject for which to retrieve the WebSphere group names
* @return the WebSphere group names for the given subject
*/
private static final String[] getWebSphereGroups(final Subject subject) {
return getWebSphereGroups(getSecurityName(subject));
}
/**
* Get the WebSphere group names for the given security name.
*
* @param securityName
* The securityname for which to retrieve the WebSphere group names
* @return the WebSphere group names for the given security name
*/
@SuppressWarnings("unchecked")
private static final String[] getWebSphereGroups(final String securityName) {
Context ic = null;
try {
// TODO: Cache UserRegistry object
ic = new InitialContext();
Object objRef = ic.lookup(USER_REGISTRY);
Object userReg = PortableRemoteObject.narrow(objRef, Class.forName ("com.ibm.websphere.security.UserRegistry"));
if (logger.isDebugEnabled()) {
logger.debug("Determining WebSphere groups for user " + securityName + " using WebSphere UserRegistry " + userReg);
}
final Collection groups = (Collection) invokeMethod(getGroupsForUserMethod(), userReg, new Object[]{ securityName });
if (logger.isDebugEnabled()) {
logger.debug("Groups for user " + securityName + ": " + groups.toString());
}
String[] result = new String[groups.size()];
return (String[]) groups.toArray(result);
} catch (Exception e) {
logger.error("Exception occured while looking up groups for user", e);
throw new RuntimeException("Exception occured while looking up groups for user", e);
} finally {
try {
ic.close();
} catch (NamingException e) {
logger.debug("Exception occured while closing context", e);
}
}
}
/**
* @return
*/
public static final String[] getGroupsForCurrentUser() {
return getWebSphereGroups(getRunAsSubject());
}
public static final String getCurrentUserName() {
return getSecurityName(getRunAsSubject());
}
private static final Object invokeMethod(Method method, Object instance, Object[] args)
{
try {
return method.invoke(instance,args);
} catch (IllegalArgumentException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+ Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
} catch (IllegalAccessException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
} catch (InvocationTargetException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
}
}
private static final Method getMethod(String className, String methodName, String[] parameterTypeNames) {
try {
Class<?> c = Class.forName(className);
final int len = parameterTypeNames.length;
Class<?>[] parameterTypes = new Class[len];
for (int i = 0; i < len; i++) {
parameterTypes[i] = Class.forName(parameterTypeNames[i]);
}
return c.getDeclaredMethod(methodName, parameterTypes);
} catch (ClassNotFoundException e) {
logger.error("Required class"+className+" not found");
throw new RuntimeException("Required class"+className+" not found",e);
} catch (NoSuchMethodException e) {
logger.error("Required method "+methodName+" with parameter types ("+ Arrays.asList(parameterTypeNames) +") not found on class "+className);
throw new RuntimeException("Required class"+className+" not found",e);
}
}
private static final Method getRunAsSubjectMethod() {
if (getRunAsSubject == null) {
getRunAsSubject = getMethod("com.ibm.websphere.security.auth.WSSubject", "getRunAsSubject", new String[] {});
}
return getRunAsSubject;
}
private static final Method getGroupsForUserMethod() {
if (getGroupsForUser == null) {
getGroupsForUser = getMethod("com.ibm.websphere.security.UserRegistry", "getGroupsForUser", new String[] { "java.lang.String" });
}
return getGroupsForUser;
}
private static final Method getSecurityNameMethod() {
if (getSecurityName == null) {
getSecurityName = getMethod("com.ibm.websphere.security.cred.WSCredential", "getSecurityName", new String[] {});
}
return getSecurityName;
}
// SEC-803
private static final Class<?> getWSCredentialClass() {
if (wsCredentialClass == null) {
wsCredentialClass = getClass("com.ibm.websphere.security.cred.WSCredential");
}
return wsCredentialClass;
}
private static final Class<?> getClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
logger.error("Required class " + className + " not found");
throw new RuntimeException("Required class " + className + " not found",e);
}
}
}

View File

@ -0,0 +1,20 @@
package org.springframework.security.web.authentication.preauth.websphere;
import java.util.List;
/**
* Provides indirection between classes using websphere and the actual container interaction,
* allowing for easier unit testing.
* <p>
* Only for internal use.
*
* @author Luke Taylor
* @version $Id$
* @since 3.0.0
*/
interface WASUsernameAndGroupsExtractor {
List<String> getGroupsForCurrentUser();
String getCurrentUserName();
}

View File

@ -14,15 +14,24 @@ import org.springframework.util.Assert;
/**
* This method interceptor can be used in front of arbitrary Spring beans to make a Spring SecurityContext
* available to the bean, based on the current WebSphere credentials.
*
*
* @author Ruud Senden
* @since 1.0
*/
public class WebSphere2SpringSecurityPropagationInterceptor implements MethodInterceptor {
private static final Log LOG = LogFactory.getLog(WebSphere2SpringSecurityPropagationInterceptor.class);
private static final Log logger = LogFactory.getLog(WebSphere2SpringSecurityPropagationInterceptor.class);
private AuthenticationManager authenticationManager = null;
private AuthenticationDetailsSource authenticationDetailsSource = new WebSpherePreAuthenticatedAuthenticationDetailsSource();
private final WASUsernameAndGroupsExtractor wasHelper;
public WebSphere2SpringSecurityPropagationInterceptor() {
this(new DefaultWASUsernameAndGroupsExtractor());
}
WebSphere2SpringSecurityPropagationInterceptor(WASUsernameAndGroupsExtractor wasHelper) {
this.wasHelper = wasHelper;
}
/**
* Authenticate with Spring Security based on WebSphere credentials before proceeding with method
* invocation, and clean up the Spring Security Context after method invocation finishes.
@ -30,63 +39,42 @@ public class WebSphere2SpringSecurityPropagationInterceptor implements MethodInt
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
try {
LOG.debug("Performing Spring Security authentication with WebSphere credentials");
logger.debug("Performing Spring Security authentication with WebSphere credentials");
authenticateSpringSecurityWithWASCredentials(this);
LOG.debug("Proceeding with method invocation");
logger.debug("Proceeding with method invocation");
return methodInvocation.proceed();
} finally {
LOG.debug("Clearing Spring Security security context");
clearSpringSecurityContext();
logger.debug("Clearing Spring Security security context");
SecurityContextHolder.clearContext();
}
}
/**
* Retrieve the current WebSphere credentials and authenticate them with Spring Security
* using the pre-authenticated authentication provider.
* @param aContext The context to use for building the authentication details.
*/
private final void authenticateSpringSecurityWithWASCredentials(Object aContext)
{
private final void authenticateSpringSecurityWithWASCredentials(Object aContext) {
Assert.notNull(authenticationManager);
Assert.notNull(authenticationDetailsSource);
String userName = WASSecurityHelper.getCurrentUserName();
if (LOG.isDebugEnabled()) { LOG.debug("Creating authentication request for user "+userName); }
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(userName,null);
String userName = wasHelper.getCurrentUserName();
if (logger.isDebugEnabled()) { logger.debug("Creating authentication request for user "+userName); }
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(userName, "N/A");
authRequest.setDetails(authenticationDetailsSource.buildDetails(null));
if (LOG.isDebugEnabled()) { LOG.debug("Authentication request for user "+userName+": "+authRequest); }
if (logger.isDebugEnabled()) { logger.debug("Authentication request for user "+userName+": "+authRequest); }
Authentication authResponse = authenticationManager.authenticate(authRequest);
if (LOG.isDebugEnabled()) { LOG.debug("Authentication response for user "+userName+": "+authResponse); }
if (logger.isDebugEnabled()) { logger.debug("Authentication response for user "+userName+": "+authResponse); }
SecurityContextHolder.getContext().setAuthentication(authResponse);
}
/**
* Clear the Spring Security Context
*/
private final void clearSpringSecurityContext()
{
SecurityContextHolder.clearContext();
}
/**
* @return Returns the authenticationManager.
*/
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
/**
* @param authenticationManager The authenticationManager to set.
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* @return Returns the authenticationDetailsSource.
*/
public AuthenticationDetailsSource getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
/**
* @param authenticationDetailsSource The authenticationDetailsSource to set.
*/

View File

@ -1,6 +1,5 @@
package org.springframework.security.web.authentication.preauth.websphere;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
@ -29,12 +28,19 @@ public class WebSpherePreAuthenticatedAuthenticationDetailsSource extends Authen
private Attributes2GrantedAuthoritiesMapper webSphereGroups2GrantedAuthoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper();
private final WASUsernameAndGroupsExtractor wasHelper;
/**
* Public constructor which overrides the default AuthenticationDetails
* class to be used.
*/
public WebSpherePreAuthenticatedAuthenticationDetailsSource() {
this(new DefaultWASUsernameAndGroupsExtractor());
}
WebSpherePreAuthenticatedAuthenticationDetailsSource(WASUsernameAndGroupsExtractor wasHelper) {
super.setClazz(PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.class);
this.wasHelper = wasHelper;
}
/**
@ -64,10 +70,10 @@ public class WebSpherePreAuthenticatedAuthenticationDetailsSource extends Authen
/**
* Get a list of Granted Authorities based on the current user's WebSphere groups.
*
* @return GrantedAuthority[] mapped from the user's WebSphere groups.
* @return authorities mapped from the user's WebSphere groups.
*/
private List<GrantedAuthority> getWebSphereGroupsBasedGrantedAuthorities() {
List<String> webSphereGroups = Arrays.asList(WASSecurityHelper.getGroupsForCurrentUser());
List<String> webSphereGroups = wasHelper.getGroupsForCurrentUser();
List<GrantedAuthority> userGas = webSphereGroups2GrantedAuthoritiesMapper.getGrantedAuthorities(webSphereGroups);
if (logger.isDebugEnabled()) {
logger.debug("WebSphere groups: " + webSphereGroups + " mapped to Granted Authorities: " + userGas);

View File

@ -6,18 +6,33 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
/**
* This AbstractPreAuthenticatedProcessingFilter implementation is based on
* WebSphere authentication. It will use the WebSphere RunAs user principal name
* WebSphere authentication. It will use the WebSphere RunAs user principal name
* as the pre-authenticated principal.
*
* @author Ruud Senden
* @since 2.0
*/
public class WebSpherePreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private final WASUsernameAndGroupsExtractor wasHelper;
/**
* Public constructor which overrides the default AuthenticationDetails
* class to be used.
*/
public WebSpherePreAuthenticatedProcessingFilter() {
this(new DefaultWASUsernameAndGroupsExtractor());
}
WebSpherePreAuthenticatedProcessingFilter(WASUsernameAndGroupsExtractor wasHelper) {
this.wasHelper = wasHelper;
}
/**
* Return the WebSphere user name.
*/
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) {
Object principal = WASSecurityHelper.getCurrentUserName();
Object principal = wasHelper.getCurrentUserName();
if (logger.isDebugEnabled()) {
logger.debug("PreAuthenticated WebSphere principal: " + principal);
}

View File

@ -0,0 +1,65 @@
package org.springframework.security.web.authentication.preauth.websphere;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.After;
import org.junit.Test;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
/**
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class WebSphere2SpringSecurityPropagationInterceptorTests {
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
/** SEC-1078 */
@Test
public void createdAuthenticationTokenIsAcceptableToPreauthProvider () throws Throwable {
WASUsernameAndGroupsExtractor helper = mock(WASUsernameAndGroupsExtractor.class);
when(helper.getCurrentUserName()).thenReturn("joe");
WebSphere2SpringSecurityPropagationInterceptor interceptor =
new WebSphere2SpringSecurityPropagationInterceptor(helper);
final SecurityContext context = new SecurityContextImpl();
interceptor.setAuthenticationManager(new AuthenticationManager() {
public Authentication authenticate(Authentication authentication) {
// Store the auth object
context.setAuthentication(authentication);
return null;
}
});
interceptor.setAuthenticationDetailsSource(mock(AuthenticationDetailsSource.class));
interceptor.invoke(mock(MethodInvocation.class));
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
AuthenticationUserDetailsService uds = mock(AuthenticationUserDetailsService.class);
UserDetails user = mock(UserDetails.class);
when(user.getAuthorities()).thenReturn(AuthorityUtils.createAuthorityList("SOME_ROLE"));
when(uds.loadUserDetails(any(Authentication.class))).thenReturn(user);
provider.setPreAuthenticatedUserDetailsService(uds);
provider.setUserDetailsChecker(mock(UserDetailsChecker.class));
assertNotNull(provider.authenticate(context.getAuthentication()));
}
}