瀏覽代碼

SEC-477: Added preauthenticated websphere contribution.

Luke Taylor 17 年之前
父節點
當前提交
350a626587

+ 55 - 0
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);
+	}
+}

+ 194 - 0
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. 
+ * <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 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;
+	}
+
+}

+ 96 - 0
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;
+	}
+}

+ 85 - 0
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;
+	}
+
+}

+ 39 - 0
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;
+    }
+}

+ 24 - 0
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);
+	}
+}