org.opensocial.explorer.server.login.GoogleLoginServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.opensocial.explorer.server.login.GoogleLoginServlet.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.opensocial.explorer.server.login;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.shindig.auth.SecurityTokenException;
import org.apache.shindig.common.servlet.Authority;
import org.apache.shindig.common.uri.UriBuilder;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.wink.json4j.JSONException;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Preconditions;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import org.apache.wink.json4j.JSONObject;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * A servlet that handles requests for logging in via Google OAuth, 
 * including handling login callbacks from the Google Authorization server.
 * 
 * <pre>
 * GET googleLogin/popup
 * - This endpoint is reached from the client-side popup when the user clicks login. 
 * - This servlet sends back a redirect to Google's login and authorization page.
 * 
 * GET googleLogin/token
 * - The callback URL from Google after the user has accepted or declined authorization.
 * 
 * - If the user has declined, Google returns an error parameter in the callback URL.
 * 1. In this case, we return some javascript code that closes the popup.
 * 
 * - If the user has accepted, Google returns a one-time auth code parameter in the callback URL.
 * 1. In this case, we make a POST back to Google with the auth code, and injected app information.
 * 2. Google will then return a JSON with an encrypted ID Token in exchange for the one time auth code.
 * 3. We unencrypt and verify this token using a Google's Java API to make sure the token is authentic.
 * 4. Once authenticated, we take the user ID from the token and generate a security token with it.
 * 5. Lastly, we return the security token client-side via some javascript, where it is also stored locally.
 * </pre>
 */
public class GoogleLoginServlet extends LoginServlet {
    private static final long serialVersionUID = -7170540545895008899L;
    private static final String GRANT_TYPE = "authorization_code";

    @Inject
    public void injectDependencies(@Named("explorer.googlelogin.clientid") String clientId,
            @Named("explorer.googlelogin.clientsecret") String clientSecret,
            @Named("explorer.googlelogin.redirecturi") String redirectUri, Authority authority) {

        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri.replaceAll("%origin%", authority.getOrigin()).replaceAll("%contextRoot%",
                contextRoot);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String[] paths = getPaths(req);

            if (paths.length == 0) {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND,
                        "Path must be one of \"googleLogin/popup\" or \"googleLogin/token\"");
                return;
            }

            // Redirect to Google Login for authentication.
            if ("popup".equals(paths[0])) {
                String destination = "https://accounts.google.com/o/oauth2/auth" + "?redirect_uri="
                        + this.redirectUri + "&client_id=" + this.clientId + "&response_type=code"
                        + "&scope=https://www.googleapis.com/auth/userinfo.profile" + "&approval_prompt=force";
                resp.sendRedirect(destination);
            }

            // Callback from Google Servers after user has accepted or declined access.
            if ("token".equals(paths[0])) {
                // If user clicked 'Decline', close the popup.
                if (req.getParameter("error") != null) {
                    this.closePopup(resp);
                    // Else, we verify the response from Google, obtain the user's ID, and generate a security token to OSE.
                } else {
                    Preconditions.checkNotNull(clientId);
                    Preconditions.checkNotNull(clientSecret);
                    Preconditions.checkNotNull(redirectUri);

                    HttpRequest googleRequest = this.constructGooglePostRequest(req);
                    HttpResponse googleResponse = fetcher.fetch(googleRequest);
                    JSONObject responseJSON = this.parseResponseToJson(googleResponse);
                    if (responseJSON.has("error")) {
                        throw new IllegalStateException();
                    }

                    String idToken = responseJSON.getString("id_token");
                    GoogleIdToken userIdToken = GoogleIdToken.parse(new JacksonFactory(), idToken);
                    GoogleIdToken.Payload googlePayload = userIdToken.getPayload();
                    String userId = googlePayload.getSubject();

                    if (googlePayload.getIssuer().equals("accounts.google.com")
                            && googlePayload.getAudience().equals(this.clientId)) {
                        this.returnSecurityToken(userId, resp);
                    } else {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid response token");
                    }
                }
            }
        } catch (GadgetException e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error making POST request.");
        } catch (JSONException e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error parsing JSON response.");
        } catch (SecurityTokenException e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error generating security token.");
        } catch (NullPointerException e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Missing app client metadata.");
        } catch (IllegalStateException e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Error making token request due to an invalid client id or secret. Please double check the credentials.");
        }
    }

    private HttpRequest constructGooglePostRequest(HttpServletRequest req)
            throws UnsupportedEncodingException, IOException {
        String authCode = req.getParameter("code");

        HttpRequest googleRequest = new HttpRequest(new UriBuilder().setScheme("https")
                .setAuthority("accounts.google.com").setPath("/o/oauth2/token").toUri());

        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        nameValuePairs.add(new BasicNameValuePair("code", authCode));
        nameValuePairs.add(new BasicNameValuePair("client_id", this.clientId));
        nameValuePairs.add(new BasicNameValuePair("client_secret", this.clientSecret));
        nameValuePairs.add(new BasicNameValuePair("redirect_uri", this.redirectUri));
        nameValuePairs.add(new BasicNameValuePair("grant_type", GRANT_TYPE));

        googleRequest.setMethod("POST");
        googleRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
        googleRequest.setPostBody(new UrlEncodedFormEntity(nameValuePairs).getContent());

        return googleRequest;
    }
}