Thursday, August 4, 2011

Integrating OpenId into my Struts 2 app - Part 4 - The OpenIdAuthenticator bean

In the final post in this series, we'll concentrate on the OpenIdAuthenticator which does the heavy lifting, with help from the OpenId4Java library, in our interaction with the user's selected OpenID Provider.

According to the OpenId4Java website, the OpenId4Java library "allows you to OpenID-enable your Java webapp."  Many thanks to the folks who put together this solid library.  I suggest checking out the sample code.  There's a number of example that will help you get started.

One of the tricky things I encountered was managing to get the OpenId provider to provide information about the logged in user.  Things like the user's first and last name, their email address, country, and language.  I was disappointed to discover the OpenID Providers (Google, Yahoo, AOL, etc) are not consistent in the level of detail they provide about the user.  Not only that but the format of the attributes are different as well.  This difference leads to the if/else section of the getValidateOpenIdUrl() method.

public class OpenIdAuthenticator {
    
    static Logger logger = Logger.getLogger(OpenIdAuthenticator.class);

    private static ConsumerManager getConsumerManager(Map<String, Object> application) {
        logger.debug("Entering getConsumerManager()");
        
        ConsumerManager manager;

        // try to get the ConsumerManager from the Application scope
        manager = (ConsumerManager)application.get("consumermanager");
        
        if (manager == null) {
            // create a new ConsumerManager
            try {
                manager = new ConsumerManager();
                manager.setAssociations(new InMemoryConsumerAssociationStore());
                manager.setNonceVerifier(new InMemoryNonceVerifier(5000));
            } catch (Exception e) {
                e.printStackTrace();
            }
        
            // add the Consumer Manager to the application scope
            application.put("consumermanager", manager);
        }

        return manager;
    }
    
    @SuppressWarnings("unchecked")
    public static String getValidateOpenIdUrl(String returnUrl, String openIdIdentifier, Map<String, Object> httpSession, Map<String, Object> application) throws DiscoveryException,
            MessageException, ConsumerException {
        
        logger.debug("Entering getOpenIdDestinationUrl()");

        // get a reference to the Consumer Manager
        ConsumerManager manager = getConsumerManager(application);
        
        // perform discovery on the user-supplied identifier
        List<DiscoveryInformation> discoveries = manager.discover(openIdIdentifier);

        // attempt to associate with the OpenID provider
        // and retrieve one service endpoint for authentication
        DiscoveryInformation discovered = manager.associate(discoveries);

        // store the discovery information in the user's session for later use
        // leave out for stateless operation / if there is no session
        httpSession.put("discovered", discovered);

        // obtain a AuthRequest message to be sent to the OpenID provider
        AuthRequest authReq = manager.authenticate(discovered, returnUrl);

        // Attribute Exchange
        FetchRequest fetch = FetchRequest.createFetchRequest();

        // different Open Id providers accept different attributes
        if (openIdIdentifier.contains("google.com")) {
            logger.debug("Open Id Identifier is: google.com");
            
            fetch.addAttribute("first", "http://axschema.org/namePerson/first", true);
            fetch.addAttribute("last", "http://axschema.org/namePerson/last", true);
            fetch.addAttribute("email", "http://axschema.org/contact/email", true);
            fetch.addAttribute("language", "http://axschema.org/pref/language", true);
        }
        else if (openIdIdentifier.contains("yahoo.com")) {
            logger.debug("Open Id Identifier is: yahoo.com");
            
            fetch.addAttribute("fullname", "http://axschema.org/namePerson", true);
            fetch.addAttribute("nickname", "http://axschema.org/namePerson/friendly", true);
            fetch.addAttribute("email", "http://axschema.org/contact/email", true);
            fetch.addAttribute("language", "http://axschema.org/pref/language", true);
        }
        else if (openIdIdentifier.contains("aol.com")) {
            logger.debug("Open Id Identifier is: aol.com");

            fetch.addAttribute("first", "http://axschema.org/namePerson/first", true);
            fetch.addAttribute("last", "http://axschema.org/namePerson/last", true);
            fetch.addAttribute("email", "http://axschema.org/contact/email", true);
            fetch.addAttribute("language", "http://axschema.org/pref/language", true);
        }
        else {
            logger.debug("Open Id Identifier is: something else");
            
            fetch.addAttribute("fullname", "http://schema.openid.net/namePerson", true); 
            fetch.addAttribute("email", "http://schema.openid.net/contact/email", true); 
            fetch.addAttribute("country", "http://axschema.org/contact/country/home", true);    
        }

        // attach the extension to the authentication request
        authReq.addExtension(fetch);

        logger.info("The request string is: " + authReq.getDestinationUrl(true).replaceAll("&", "\n"));

        return authReq.getDestinationUrl(true);
    }
    
    public static User getAuthenticatedUser(Map<String,String[]> parmList,
            final StringBuffer receivingURL, Map<String, Object> httpSession, Map<String, Object> application)
            throws MessageException, DiscoveryException, AssociationException {

        logger.debug("Entering getAuthenticatedUser()");

        // extract the parameters from the authentication response
        // (which comes in as a HTTP request from the OpenID provider)
        ParameterList openidResp = new ParameterList(parmList);
        
        // retrieve the previously stored discovery information
        final DiscoveryInformation discovered = (DiscoveryInformation) httpSession.get("discovered");

        // get a reference to the Consumer Manager
        ConsumerManager manager = getConsumerManager(application);
        
        // verify the response
        final VerificationResult verification = manager.verify(receivingURL.toString(), openidResp, discovered);
        
        // examine the verification result and extract the verified identifier
        Identifier verified = verification.getVerifiedId();
        if (verified == null) {
            return null;
        }

        AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse();

        User user = new User();
        user.setOpenid(authSuccess.getIdentity());
        
        if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
            logger.info("Processed as OPENID_NS_AX");
            
            FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX);

            // populate the User object with attributes from the FetchResponse
            user.setNickname(fetchResp.getAttributeValue("nickname"));
            user.setEmail(fetchResp.getAttributeValue("email"));
            user.setFullName(fetchResp.getAttributeValue("fullname"));
            user.setFirstName(fetchResp.getAttributeValue("first"));
            user.setLastName(fetchResp.getAttributeValue("last"));
            user.setLanguage(fetchResp.getAttributeValue("language"));
            user.setCountry(fetchResp.getAttributeValue("country"));

            logger.info("User: " + user.toString());
        }

        if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
            logger.info("Processed as OPENID_NS_SREG");
            
            SRegResponse sregResp = (SRegResponse) authSuccess.getExtension(SRegMessage.OPENID_NS_SREG);

            // if we didn't get the user's email addy from the FetchResponse, try to get it from the SRegResponse 
            if (StringUtils.isBlank(user.getEmail())) {
                user.setEmail(sregResp.getAttributeValue("email"));
            }
        }
        return user;
    }
}

9 comments:

  1. Thanks a lot,
    It really helped me understand the steps of implementation of OpenId with Struts2... gonna try it now....in my project.... was short of puzzled after reading lots of other online contents and ways of implementations. Really thanks for a short and useful guide.

    You should be writing more of these implementation guides... its a long time no updates....

    ReplyDelete
  2. sushant, i'm glad you found the post helpful

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi, Thank you for detailed post in this. Would you have the working version of this project, it will be really helpful to run through the flow. Thanks :)

    regards,
    Vasanth.

    ReplyDelete
  5. Really thanks ....it helped me a lot please provide the source code download for this...

    ReplyDelete
  6. You can find an implementation of this in my Github project. https://github.com/justinhrobbins/FlashCards_App/blob/master/FlashCards_Struts/src/main/java/org/robbins/openid/authentication/OpenId4JavaAuthenticator.java

    ReplyDelete
  7. Thanks a lot with this cool implementation of Struts 2. May I know does this applicable to latest OAuth2 & OIDC?

    ReplyDelete