简体   繁体   中英

How to do oauth2 (to Google) with servlets? (simple way to do sign-in with Google in Java)

I did use OpenID 2.0 which is going to be deprecated (obsolete) in 2015 for Google. What is a simple way to migrate if I'm using servlets?

I spent several hours reading from Google website and if you think that Google API Client is unfriendly or over complex you should continue reading this. I took look at Apache Oltu, however, the documentation doesn't have example for Google and also I've found several people complaining. Finally, after several hours of frustration and Googling, somehow I stumbled across great post

http://highaltitudedev.blogspot.com/2013/10/google-oauth2-with-jettyservlets.html

So I accomodated it to my needs.

This is how I build login request :

  public static String getOauthUrlGoogle(HttpServletRequest request) {
    String redirectUrl = "http://www.tralev.com/web/oauth2callback";
    if (Html.isRunningLocally()) {
      redirectUrl = "http://localhost:8080/TralevServer/web/oauth2callback";
    }
    StringBuilder oauthUrl = new StringBuilder();
    oauthUrl.append("https://accounts.google.com/o/oauth2/auth")
      .append("?client_id=").append(GOOGLE_CLIENT_ID) // the client id from the api console registration
      .append("&response_type=code")
      .append("&scope=profile%20email") // scope is the api permissions we are requesting
      .append("&redirect_uri=").append(redirectUrl) // the servlet that google redirects to after authorization
      .append("&state=").append(request.getSession().getId())
      //.append("&access_type=offline") // here we are asking to access to user's data while they are not signed in
      .append("&approval_prompt=force"); // this requires them to verify which account to use, if they are already signed in
    return oauthUrl.toString();
  }

And this is Servlet which performs the rest:

package com.tralev.server.web;

import commons.StringUtils;
import commons.UsualHtmlUtils;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 *
 * @author mladen
 */
@WebServlet(name = "OAuth2CallbackServlet", urlPatterns = {"/web/oauth2callback"})
public class OAuth2CallbackServlet extends HttpServlet {

  public static String GOOGLE_CLIENT_ID = "your id";
  public static String GOOGLE_CLIENT_SECRET = "your secret";

  public String getCallbackUrl() {
    String redirectUrl = "http://www.tralev.com/web/oauth2callback";
    if (Html.isRunningLocally()) {
      redirectUrl = "http://localhost:8080/TralevServer/web/oauth2callback";
    }
    return redirectUrl;
  }

  /**
   * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  protected void processRequest(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    // google redirects with
    //http://localhost:8089/callback?state=this_can_be_anything_to_help_correlate_the_response%3Dlike_session_id&code=4/ygE-kCdJ_pgwb1mKZq3uaTEWLUBd.slJWq1jM9mcUEnp6UAPFm0F2NQjrgwI&authuser=0&prompt=consent&session_state=a3d1eb134189705e9acf2f573325e6f30dd30ee4..d62c

   // if the user denied access, we get back an error, ex
    // error=access_denied&state=session%3Dpotatoes
    if (req.getParameter("error") != null) {
      Html.printPage(req, resp, "Error", StringUtils.toString(req.getParameter("error")));
    } else {

      // google returns a code that can be exchanged for a access token
      String code = req.getParameter("code");

      LinkedHashMap<String, String> params = new LinkedHashMap<String, String>();

      params.put("code", code);
      params.put("client_id", GOOGLE_CLIENT_ID);
      params.put("client_secret", GOOGLE_CLIENT_SECRET);
      params.put("redirect_uri", getCallbackUrl());
      params.put("grant_type", "authorization_code");

      // get the access token by post to Google
      String body = post("https://accounts.google.com/o/oauth2/token", params);

   // ex. returns
//   {
//       "access_token": "ya29.AHES6ZQS-BsKiPxdU_iKChTsaGCYZGcuqhm_A5bef8ksNoU",
//       "token_type": "Bearer",
//       "expires_in": 3600,
//       "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA5ZmE5NmFjZWNkOGQyZWRjZmFiMjk0NDRhOTgyN2UwZmFiODlhYTYifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiZW1haWwiOiJhbmRyZXcucmFwcEBnbWFpbC5jb20iLCJhdWQiOiI1MDgxNzA4MjE1MDIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdF9oYXNoIjoieUpVTFp3UjVDX2ZmWmozWkNublJvZyIsInN1YiI6IjExODM4NTYyMDEzNDczMjQzMTYzOSIsImF6cCI6IjUwODE3MDgyMTUwMi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImlhdCI6MTM4Mjc0MjAzNSwiZXhwIjoxMzgyNzQ1OTM1fQ.Va3kePMh1FlhT1QBdLGgjuaiI3pM9xv9zWGMA9cbbzdr6Tkdy9E-8kHqrFg7cRiQkKt4OKp3M9H60Acw_H15sV6MiOah4vhJcxt0l4-08-A84inI4rsnFn5hp8b-dJKVyxw1Dj1tocgwnYI03czUV3cVqt9wptG34vTEcV3dsU8",
//       "refresh_token": "1/Hc1oTSLuw7NMc3qSQMTNqN6MlmgVafc78IZaGhwYS-o"
//   }
      JSONObject jsonObject = null;

      // get the access token from json and request info from Google
      try {
        jsonObject = (JSONObject) new JSONParser().parse(body);
      } catch (ParseException e) {
        throw new RuntimeException("Unable to parse json " + body);
      }

      // google tokens expire after an hour, but since we requested offline access we can get a new token without user involvement via the refresh token
      String accessToken = (String) jsonObject.get("access_token");

      // you may want to store the access token in session
      req.getSession().setAttribute("access_token", accessToken);

      // get some info about the user with the access token
      String json = get(new StringBuilder("https://www.googleapis.com/oauth2/v1/userinfo?access_token=").append(accessToken).toString());
      try {
        jsonObject = (JSONObject) new JSONParser().parse(json);
      } catch (ParseException e) {
        throw new RuntimeException("Unable to parse json " + body);
      }

      String email = (String) jsonObject.get("email");
      String fullName = (String) jsonObject.get("name");
      Logger.getLogger(this.getClass().getName()).log(Level.INFO, "email= {0} ,name={1}", new Object[]{email, fullName}); 
     //do the rest stuff

    }
  }

  // makes a GET request to url and returns body as a string
  public String get(String url) throws ClientProtocolException, IOException {
    return execute(new HttpGet(url));
  }

  // makes a POST request to url with form parameters and returns body as a string
  public String post(String url, Map<String, String> formParameters) throws ClientProtocolException, IOException {
    HttpPost request = new HttpPost(url);

    List<NameValuePair> nvps = new ArrayList<NameValuePair>();

    for (String key : formParameters.keySet()) {
      nvps.add(new BasicNameValuePair(key, formParameters.get(key)));
    }

    request.setEntity(new UrlEncodedFormEntity(nvps));

    return execute(request);
  }

  // makes request and checks response code for 200
  private String execute(HttpRequestBase request) throws ClientProtocolException, IOException {
    HttpClient httpClient = new DefaultHttpClient();
    HttpResponse response = httpClient.execute(request);

    HttpEntity entity = response.getEntity();
    String body = EntityUtils.toString(entity);

    if (response.getStatusLine().getStatusCode() != 200) {
      throw new RuntimeException("Expected 200 but got " + response.getStatusLine().getStatusCode() + ", with body " + body);
    }

    return body;
  }

  // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  /**
   * Handles the HTTP <code>GET</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Handles the HTTP <code>POST</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Returns a short description of the servlet.
   *
   * @return a String containing servlet description
   */
  @Override
  public String getServletInfo() {
    return "Short description";
  }// </editor-fold>

  private String getAfterLoginUrl(HttpServletRequest req) {
    String afterLoginUrl = "http://www.tralev.com/web/main";
    if (Html.isRunningLocally()) {
      afterLoginUrl = "http://localhost:8080/TralevServer/web/main";
    }
    return afterLoginUrl;
  }

}

Hope you'll find this useful or original post, I spent many hours looking into other overcomplex libraries and this was exactly what I was looking for.

Here is an example of servlet app that supports "login with Google".You can do OAuth2 to Google with servlets.

https://github.com/riversun/google-login-servlet-example-on-jetty.git

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM