Browse Source

OpenId4Java support
OpenIdAuthenticationProcessingFilter to replace OpenIDLoginInititationServlet

Ray Krueger 18 years ago
parent
commit
1dfc2baea2

+ 98 - 1
sandbox/openid/pom.xml

@@ -4,7 +4,7 @@
     <parent>
         <groupId>org.acegisecurity</groupId>
         <artifactId>acegi-security-sandbox</artifactId>
-        <version>1.0.4-SNAPSHOT</version>
+        <version>1.0.5-SNAPSHOT</version>
     </parent>
     <artifactId>acegi-security-openid</artifactId>
     <name>Acegi Security System for Spring - OpenID support</name>
@@ -37,6 +37,11 @@
           <artifactId>spring-mock</artifactId>
           <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.openid4java</groupId>
+            <artifactId>openid4java</artifactId>
+            <version>0.9.2</version>
+        </dependency>
         <dependency>
             <groupId>com.janrain</groupId>
             <artifactId>Janrain-Openid</artifactId>
@@ -62,6 +67,98 @@
             <scope>provided</scope>
         </dependency>
 -->
+
+        <!--openid4java dependencies-->
+        <!--
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.htmlparser</groupId>
+            <artifactId>htmlparser</artifactId>
+            <version>1.6</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ibm.icu</groupId>
+            <artifactId>icu4j</artifactId>
+            <version>3.4.4</version>
+        </dependency>
+        <dependency>
+            <groupId>jug</groupId>
+            <artifactId>jug</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openxri</groupId>
+            <artifactId>openxri-client</artifactId>
+            <version>1.0.1</version>
+            <scope>system</scope>
+            <systemPath>${basedir}/lib/openxri-client.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>org.openxri</groupId>
+            <artifactId>openxri-syntax</artifactId>
+            <version>1.0.1</version>
+            <scope>system</scope>
+            <systemPath>${basedir}/lib/openxri-syntax.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring</artifactId>
+            <version>2.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>xml-security</groupId>
+            <artifactId>xmlsec</artifactId>
+            <version>1.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>jdom</groupId>
+            <artifactId>jdom</artifactId>
+            <version>1.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>6.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>6.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+            <version>2.8.1</version>
+            <scope>test</scope>
+        </dependency>
+-->
     </dependencies>
 
     <!--This doesn't even exist...-->

+ 2 - 5
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);
     }
 }

+ 4 - 30
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);
 }

+ 1 - 1
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) {

+ 186 - 0
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. <br/
+     * > 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);
+    }
+}

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

+ 2 - 2
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);

+ 135 - 0
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() + "]");
+        }
+    }
+}

+ 1 - 1
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;
     }