diff --git a/core/src/main/java/org/springframework/security/ui/preauth/PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.java b/core/src/main/java/org/springframework/security/ui/preauth/PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.java new file mode 100755 index 0000000000..f124b1ed11 --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.java @@ -0,0 +1,55 @@ +package org.springframework.security.ui.preauth; + +import java.util.Arrays; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.MutableGrantedAuthoritiesContainer; +import org.springframework.security.ui.AuthenticationDetails; +import org.springframework.util.Assert; + +/** + * This AuthenticationDetails implementation allows for storing a list of + * pre-authenticated Granted Authorities. + * + * @author Ruud Senden + * @since 2.0 + */ +public class PreAuthenticatedGrantedAuthoritiesAuthenticationDetails extends AuthenticationDetails implements + MutableGrantedAuthoritiesContainer { + public static final long serialVersionUID = 1L; + + private GrantedAuthority[] preAuthenticatedGrantedAuthorities = null; + + public PreAuthenticatedGrantedAuthoritiesAuthenticationDetails(Object context) { + super(context); + } + + /** + * @return The String representation of this object. + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(super.toString() + "; "); + sb.append("preAuthenticatedGrantedAuthorities: " + Arrays.asList(preAuthenticatedGrantedAuthorities)); + return sb.toString(); + } + + /** + * + * @see org.springframework.security.GrantedAuthoritiesContainer#getGrantedAuthorities() + */ + public GrantedAuthority[] getGrantedAuthorities() { + Assert.notNull(preAuthenticatedGrantedAuthorities, "Pre-authenticated granted authorities have not been set"); + GrantedAuthority[] result = new GrantedAuthority[preAuthenticatedGrantedAuthorities.length]; + System.arraycopy(preAuthenticatedGrantedAuthorities, 0, result, 0, result.length); + return result; + } + + /** + * @see org.springframework.security.MutableGrantedAuthoritiesContainer#setGrantedAuthorities() + */ + public void setGrantedAuthorities(GrantedAuthority[] aJ2eeBasedGrantedAuthorities) { + this.preAuthenticatedGrantedAuthorities = new GrantedAuthority[aJ2eeBasedGrantedAuthorities.length]; + System.arraycopy(aJ2eeBasedGrantedAuthorities, 0, preAuthenticatedGrantedAuthorities, 0, preAuthenticatedGrantedAuthorities.length); + } +} diff --git a/core/src/main/java/org/springframework/security/ui/preauth/websphere/WASSecurityHelper.java b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WASSecurityHelper.java new file mode 100755 index 0000000000..35b3f0ff8c --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WASSecurityHelper.java @@ -0,0 +1,194 @@ +package org.springframework.security.ui.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. + *

+ * 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 getWSCredentialFromSubject = null; + + private static Method getGroupsForUser = null; + + private static Method getSecurityName = 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) { + Object credential = invokeMethod(getWSCredentialFromSubjectMethod(),null,new Object[]{subject}); + 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 + */ + 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 getWSCredentialFromSubjectMethod() { + if (getWSCredentialFromSubject == null) { + getWSCredentialFromSubject = getMethod("com.ibm.ws.security.auth.SubjectHelper", "getWSCredentialFromSubject", + new String[] { "javax.security.auth.Subject" }); + } + return getWSCredentialFromSubject; + } + + 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; + } + +} diff --git a/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSphere2SpringSecurityPropagationInterceptor.java b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSphere2SpringSecurityPropagationInterceptor.java new file mode 100755 index 0000000000..dcd9007f99 --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSphere2SpringSecurityPropagationInterceptor.java @@ -0,0 +1,96 @@ +package org.springframework.security.ui.preauth.websphere; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationManager; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.security.ui.AuthenticationDetailsSource; +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 AuthenticationManager authenticationManager = null; + private AuthenticationDetailsSource authenticationDetailsSource = new WebSpherePreAuthenticatedAuthenticationDetailsSource(); + + /** + * Authenticate with Spring Security based on WebSphere credentials before proceeding with method + * invocation, and clean up the Spring Security Context after method invocation finishes. + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + try { + LOG.debug("Performing Spring Security authentication with WebSphere credentials"); + authenticateSpringSecurityWithWASCredentials(this); + LOG.debug("Proceeding with method invocation"); + return methodInvocation.proceed(); + } finally { + LOG.debug("Clearing Spring Security security context"); + clearSpringSecurityContext(); + } + } + + /** + * 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) + { + 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); + authRequest.setDetails(authenticationDetailsSource.buildDetails(null)); + if (LOG.isDebugEnabled()) { LOG.debug("Authentication request for user "+userName+": "+authRequest); } + Authentication authResponse = authenticationManager.authenticate(authRequest); + if (LOG.isDebugEnabled()) { LOG.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. + */ + public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { + this.authenticationDetailsSource = authenticationDetailsSource; + } +} diff --git a/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedAuthenticationDetailsSource.java b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedAuthenticationDetailsSource.java new file mode 100755 index 0000000000..c4d55770c7 --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedAuthenticationDetailsSource.java @@ -0,0 +1,85 @@ +package org.springframework.security.ui.preauth.websphere; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.MutableGrantedAuthoritiesContainer; +import org.springframework.security.authoritymapping.Attributes2GrantedAuthoritiesMapper; +import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper; +import org.springframework.security.ui.AuthenticationDetailsSourceImpl; +import org.springframework.security.ui.preauth.PreAuthenticatedGrantedAuthoritiesAuthenticationDetails; +import org.springframework.util.Assert; + +/** + * This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer, + * will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere + * user, mapped using the configured Attributes2GrantedAuthoritiesMapper. + * + * By default, this class is configured to build instances of the + * PreAuthenticatedGrantedAuthoritiesAuthenticationDetails class. + * + * @author Ruud Senden + */ +public class WebSpherePreAuthenticatedAuthenticationDetailsSource extends AuthenticationDetailsSourceImpl implements InitializingBean { + private static final Log LOG = LogFactory.getLog(WebSpherePreAuthenticatedAuthenticationDetailsSource.class); + + private Attributes2GrantedAuthoritiesMapper webSphereGroups2GrantedAuthoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper(); + + /** + * Public constructor which overrides the default AuthenticationDetails + * class to be used. + */ + public WebSpherePreAuthenticatedAuthenticationDetailsSource() { + super.setClazz(PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.class); + } + + /** + * Check that all required properties have been set. + */ + public void afterPropertiesSet() throws Exception { + Assert.notNull(webSphereGroups2GrantedAuthoritiesMapper, "WebSphere groups to granted authorities mapper not set"); + } + + /** + * Build the authentication details object. If the speficied authentication + * details class implements the PreAuthenticatedGrantedAuthoritiesSetter, a + * list of pre-authenticated Granted Authorities will be set based on the + * WebSphere groups for the current user. + * + * @see org.springframework.security.ui.AuthenticationDetailsSource#buildDetails(Object) + */ + public Object buildDetails(Object context) { + Object result = super.buildDetails(context); + if (result instanceof MutableGrantedAuthoritiesContainer) { + ((MutableGrantedAuthoritiesContainer) result) + .setGrantedAuthorities(getWebSphereGroupsBasedGrantedAuthorities()); + } + return result; + } + + /** + * Get a list of Granted Authorities based on the current user's WebSphere groups. + * + * @return GrantedAuthority[] mapped from the user's WebSphere groups. + */ + private GrantedAuthority[] getWebSphereGroupsBasedGrantedAuthorities() { + String[] webSphereGroups = WASSecurityHelper.getGroupsForCurrentUser(); + GrantedAuthority[] userGas = webSphereGroups2GrantedAuthoritiesMapper.getGrantedAuthorities(webSphereGroups); + if (LOG.isDebugEnabled()) { + LOG.debug("WebSphere groups [" + ArrayUtils.toString(webSphereGroups) + "] mapped to Granted Authorities: [" + + ArrayUtils.toString(userGas) + "]"); + } + return userGas; + } + + /** + * @param mapper + * The Attributes2GrantedAuthoritiesMapper to use + */ + public void setWebSphereGroups2GrantedAuthoritiesMapper(Attributes2GrantedAuthoritiesMapper mapper) { + webSphereGroups2GrantedAuthoritiesMapper = mapper; + } + +} diff --git a/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java new file mode 100755 index 0000000000..9b9857b9b7 --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java @@ -0,0 +1,39 @@ +package org.springframework.security.ui.preauth.websphere; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter; + +/** + * This AbstractPreAuthenticatedProcessingFilter implementation is based on + * 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 { + /** + * Return the WebSphere user name. + */ + protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) { + Object principal = WASSecurityHelper.getCurrentUserName(); + if (logger.isDebugEnabled()) { + logger.debug("PreAuthenticated WebSphere principal: " + principal); + } + return principal; + } + + /** + * For J2EE container-based authentication there is no generic way to + * retrieve the credentials, as such this method returns a fixed dummy + * value. + */ + protected Object getPreAuthenticatedCredentials(HttpServletRequest httpRequest) { + return "N/A"; + } + + public int getOrder() { + return 0; + } +} diff --git a/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedWebAuthenticationDetailsSource.java b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedWebAuthenticationDetailsSource.java new file mode 100755 index 0000000000..f093bdbcbd --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/preauth/websphere/WebSpherePreAuthenticatedWebAuthenticationDetailsSource.java @@ -0,0 +1,24 @@ +package org.springframework.security.ui.preauth.websphere; + +import org.springframework.security.ui.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; + +/** + * This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer, + * will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere + * user, mapped using the configured Attributes2GrantedAuthoritiesMapper. + * + * By default, this class is configured to build instances of the + * PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails class. + * + * @author Ruud Senden + */ +public class WebSpherePreAuthenticatedWebAuthenticationDetailsSource extends WebSpherePreAuthenticatedAuthenticationDetailsSource { + /** + * Public constructor which overrides the default AuthenticationDetails + * class to be used. + */ + public WebSpherePreAuthenticatedWebAuthenticationDetailsSource() { + super(); + super.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class); + } +}