diff --git a/sandbox/openid/pom.xml b/sandbox/openid/pom.xml index 252a3b720b..7c16655339 100644 --- a/sandbox/openid/pom.xml +++ b/sandbox/openid/pom.xml @@ -4,7 +4,7 @@ org.acegisecurity acegi-security-sandbox - 1.0.4-SNAPSHOT + 1.0.5-SNAPSHOT acegi-security-openid Acegi Security System for Spring - OpenID support @@ -37,6 +37,11 @@ spring-mock true + + org.openid4java + openid4java + 0.9.2 + com.janrain Janrain-Openid @@ -62,6 +67,98 @@ provided --> + + + diff --git a/sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java b/sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java index 516c9cabf1..20e87e620b 100644 --- a/sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java +++ b/sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java @@ -50,6 +50,7 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { + if (!supports(authentication.getClass())) { return null; } @@ -99,10 +100,6 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini * @see org.acegisecurity.providers.AuthenticationProvider#supports(java.lang.Class) */ public boolean supports(Class authentication) { - if (OpenIDAuthenticationToken.class.isAssignableFrom(authentication)) { - return true; - } else { - return false; - } + return OpenIDAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java index a28b2d7674..0506fb8ec1 100644 --- a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java @@ -22,41 +22,15 @@ import javax.servlet.http.HttpServletRequest; /** * An interface for OpenID library implementations * + * @author Ray Krueger * @author Robin Bramley, Opsera Ltd - * */ public interface OpenIDConsumer { - //~ Methods ======================================================================================================== - /** - * Start the authentication process - * - * @param req - * @param identityUrl - * - * @return redirection URL - * - * @throws OpenIDConsumerException - */ - public String beginConsumption(HttpServletRequest req, String identityUrl) - throws OpenIDConsumerException; + public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl) + throws OpenIDConsumerException; - /** - * DOCUMENT ME! - * - * @param req - * - * @return - * - * @throws OpenIDConsumerException - */ public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) - throws OpenIDConsumerException; + throws OpenIDConsumerException; - /** - * DOCUMENT ME! - * - * @param returnToUrl - */ - public void setReturnToUrl(String returnToUrl); } diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java index eacf237e4c..65b620100e 100644 --- a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java @@ -167,7 +167,7 @@ public class OpenIDLoginInitiationServlet extends HttpServlet { } else { // send the user the redirect url to proceed with OpenID authentication try { - String redirect = consumer.beginConsumption(req, id); + String redirect = consumer.beginConsumption(req, id, req.getRequestURL().toString()); logger.debug("Redirecting to: " + redirect); res.sendRedirect(redirect); } catch (OpenIDConsumerException oice) { diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationProcessingFilter.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationProcessingFilter.java new file mode 100644 index 0000000000..ffef5a212f --- /dev/null +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationProcessingFilter.java @@ -0,0 +1,186 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * 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.acegisecurity.ui.openid; + +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.AuthenticationServiceException; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.openid.OpenIDAuthenticationToken; +import org.acegisecurity.ui.AbstractProcessingFilter; +import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +/** + * DOCUMENT ME! + * + * @author $author$ + * @version $Revision$ + */ +public class OpenIdAuthenticationProcessingFilter extends AbstractProcessingFilter { + //~ Static fields/initializers ===================================================================================== + + private static final Log log = LogFactory.getLog(OpenIdAuthenticationProcessingFilter.class); + public static final String DEFAULT_CLAMED_IDENTITY_FIELD = "j_username"; + + //~ Instance fields ================================================================================================ + + private OpenIDConsumer consumer; + private String claimedIdentityFieldName = DEFAULT_CLAMED_IDENTITY_FIELD; + private String errorPage = "index.jsp"; + + //~ Methods ======================================================================================================== + + public Authentication attemptAuthentication(HttpServletRequest req) + throws AuthenticationException { + OpenIDAuthenticationToken token; + + String identity = req.getParameter("openid.identity"); + + if (!StringUtils.hasText(identity)) { + throw new OpenIdAuthenticationRequiredException("External Authentication Required", obtainUsername(req)); + } + + try { + token = consumer.endConsumption(req); + } catch (OpenIDConsumerException oice) { + throw new AuthenticationServiceException("Consumer error", oice); + } + + // delegate to the auth provider + Authentication authentication = this.getAuthenticationManager().authenticate(token); + + if (authentication.isAuthenticated()) { + req.getSession() + .setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY, token.getIdentityUrl()); + } + + return authentication; + } + + protected String determineFailureUrl(HttpServletRequest request, AuthenticationException failed) { + if (failed instanceof OpenIdAuthenticationRequiredException) { + OpenIdAuthenticationRequiredException openIdRequiredException = (OpenIdAuthenticationRequiredException) failed; + String claimedIdentity = openIdRequiredException.getClaimedIdentity(); + + if (StringUtils.hasText(claimedIdentity)) { + try { + String returnToUrl = buildReturnToUrl(request); + return consumer.beginConsumption(request, claimedIdentity, returnToUrl); + } catch (OpenIDConsumerException e) { + log.error("Unable to consume claimedIdentity [" + claimedIdentity + "]", e); + } + } + } + + return super.determineFailureUrl(request, failed); + } + + protected String buildReturnToUrl(HttpServletRequest request) { + return request.getRequestURL().toString(); + } + + public String getClaimedIdentityFieldName() { + return claimedIdentityFieldName; + } + + public OpenIDConsumer getConsumer() { + return consumer; + } + + public String getDefaultFilterProcessesUrl() { + return "/j_acegi_openid_security_check"; + } + + public String getErrorPage() { + return errorPage; + } + + protected boolean isAuthenticated(HttpServletRequest request) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + return (auth != null) && auth.isAuthenticated(); + } + + /** + * The OpenIdAuthenticationProcessingFilter will ignore the request coming in if this method returns false. + * The default functionality checks if the request scheme starts with http.
This method should be overridden in subclasses that wish to consider a different strategy + * + * @param request HttpServletRequest we're processing + * @return true if this request is determined to be an OpenID request. + */ + protected boolean isOpenIdRequest(HttpServletRequest request) { + String username = obtainUsername(request); + return (StringUtils.hasText(username)) && username.toLowerCase().startsWith("http"); + } + + protected String obtainUsername(HttpServletRequest req) { + return req.getParameter(claimedIdentityFieldName); + } + + protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException { + if (failed instanceof OpenIdAuthenticationRequiredException) { + OpenIdAuthenticationRequiredException openIdAuthenticationRequiredException = (OpenIdAuthenticationRequiredException) failed; + request.setAttribute(OpenIdAuthenticationRequiredException.class.getName(), + openIdAuthenticationRequiredException.getClaimedIdentity()); + } + } + + public void setClaimedIdentityFieldName(String claimedIdentityFieldName) { + this.claimedIdentityFieldName = claimedIdentityFieldName; + } + + public void setConsumer(OpenIDConsumer consumer) { + this.consumer = consumer; + } + + public void setErrorPage(String errorPage) { + this.errorPage = errorPage; + } + + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException { + SecurityContextHolder.getContext().setAuthentication(null); + + if (logger.isDebugEnabled()) { + logger.debug("Updated SecurityContextHolder to contain null Authentication"); + } + + String failureUrl = determineFailureUrl(request, failed); + + if (logger.isDebugEnabled()) { + logger.debug("Authentication request failed: " + failed.toString()); + } + + try { + request.getSession().setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY, failed); + } catch (Exception ignored) { + } + + super.getRememberMeServices().loginFail(request, response); + + sendRedirect(request, response, failureUrl); + } +} diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationRequiredException.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationRequiredException.java new file mode 100644 index 0000000000..315468057f --- /dev/null +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIdAuthenticationRequiredException.java @@ -0,0 +1,20 @@ +package org.acegisecurity.ui.openid; + +import org.acegisecurity.AuthenticationException; + +/** + * @author Ray Krueger + */ +public class OpenIdAuthenticationRequiredException extends AuthenticationException { + + private final String claimedIdentity; + + public OpenIdAuthenticationRequiredException(String msg, String claimedIdentity) { + super(msg); + this.claimedIdentity = claimedIdentity; + } + + public String getClaimedIdentity() { + return claimedIdentity; + } +} diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java index b275ab0095..8820cf26a8 100644 --- a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java @@ -66,7 +66,7 @@ public class JanRainOpenIDConsumer implements OpenIDConsumer, InitializingBean { /* (non-Javadoc) * @see org.acegisecurity.ui.openid.OpenIDConsumer#beginConsumption(java.lang.String) */ - public String beginConsumption(HttpServletRequest req, String identityUrl) + public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl) throws OpenIDConsumerException { // fetch/create a session Map for the consumer's use HttpSession session = req.getSession(); @@ -103,7 +103,7 @@ public class JanRainOpenIDConsumer implements OpenIDConsumer, InitializingBean { cp = cp.substring(1) + "/"; } - String returnTo = trustRoot + cp + returnToUrl; + String returnTo = trustRoot + cp + this.returnToUrl; // send the user the redirect url to proceed with OpenID authentication return ar.redirectUrl(trustRoot, returnTo); diff --git a/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/OpenId4JavaConsumer.java b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/OpenId4JavaConsumer.java new file mode 100644 index 0000000000..e0194786ef --- /dev/null +++ b/sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/OpenId4JavaConsumer.java @@ -0,0 +1,135 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * 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.acegisecurity.ui.openid.consumers; + +import org.acegisecurity.providers.openid.OpenIDAuthenticationStatus; +import org.acegisecurity.providers.openid.OpenIDAuthenticationToken; + +import org.acegisecurity.ui.openid.OpenIDConsumer; +import org.acegisecurity.ui.openid.OpenIDConsumerException; + +import org.openid4java.association.AssociationException; + +import org.openid4java.consumer.ConsumerException; +import org.openid4java.consumer.ConsumerManager; +import org.openid4java.consumer.VerificationResult; + +import org.openid4java.discovery.DiscoveryException; +import org.openid4java.discovery.DiscoveryInformation; +import org.openid4java.discovery.Identifier; + +import org.openid4java.message.AuthRequest; +import org.openid4java.message.MessageException; +import org.openid4java.message.ParameterList; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + + +/** + * DOCUMENT ME! + * + * @author Ray Krueger + */ +public class OpenId4JavaConsumer implements OpenIDConsumer { + //~ Instance fields ================================================================================================ + + private final ConsumerManager consumerManager; + + //~ Constructors =================================================================================================== + + public OpenId4JavaConsumer(ConsumerManager consumerManager) { + this.consumerManager = consumerManager; + } + + public OpenId4JavaConsumer() throws ConsumerException { + this(new ConsumerManager()); + } + + //~ Methods ======================================================================================================== + + public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl) + throws OpenIDConsumerException { + List discoveries; + + try { + discoveries = consumerManager.discover(identityUrl); + } catch (DiscoveryException e) { + throw new OpenIDConsumerException("Error during discovery", e); + } + + DiscoveryInformation information = consumerManager.associate(discoveries); + HttpSession session = req.getSession(true); + session.setAttribute(DiscoveryInformation.class.getName(), information); + + AuthRequest authReq; + + try { + authReq = consumerManager.authenticate(information, returnToUrl); + } catch (MessageException e) { + throw new OpenIDConsumerException("Error processing ConumerManager authentication", e); + } catch (ConsumerException e) { + throw new OpenIDConsumerException("Error processing ConumerManager authentication", e); + } + + return authReq.getDestinationUrl(true); + } + + public OpenIDAuthenticationToken endConsumption(HttpServletRequest request) + throws OpenIDConsumerException { + // extract the parameters from the authentication response + // (which comes in as a HTTP request from the OpenID provider) + ParameterList openidResp = new ParameterList(request.getParameterMap()); + + // retrieve the previously stored discovery information + DiscoveryInformation discovered = (DiscoveryInformation) request.getSession() + .getAttribute(DiscoveryInformation.class.getName()); + + // extract the receiving URL from the HTTP request + StringBuffer receivingURL = request.getRequestURL(); + String queryString = request.getQueryString(); + + if ((queryString != null) && (queryString.length() > 0)) { + receivingURL.append("?").append(request.getQueryString()); + } + + // verify the response + VerificationResult verification; + + try { + verification = consumerManager.verify(receivingURL.toString(), openidResp, discovered); + } catch (MessageException e) { + throw new OpenIDConsumerException("Error verifying openid response", e); + } catch (DiscoveryException e) { + throw new OpenIDConsumerException("Error verifying openid response", e); + } catch (AssociationException e) { + throw new OpenIDConsumerException("Error verifying openid response", e); + } + + // examine the verification result and extract the verified identifier + Identifier verified = verification.getVerifiedId(); + + if (verified != null) { + return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, verified.getIdentifier(), + "some message"); + } else { + return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE, + discovered.getClaimedIdentifier().getIdentifier(), + "Verification status message: [" + verification.getStatusMsg() + "]"); + } + } +} diff --git a/sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java b/sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java index 616d63ae06..a0cd667b01 100644 --- a/sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java +++ b/sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java @@ -38,7 +38,7 @@ public class MockOpenIDConsumer implements OpenIDConsumer { /* (non-Javadoc) * @see org.acegisecurity.ui.openid.OpenIDConsumer#beginConsumption(javax.servlet.http.HttpServletRequest, java.lang.String) */ - public String beginConsumption(HttpServletRequest req, String identityUrl) + public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl) throws OpenIDConsumerException { return redirectUrl; }