openplanning

Hướng dẫn sử dụng Scribe OAuth Java API với Google OAuth 2

  1. Vấn đề với Google Oauth1a
  2. Tạo ứng dụng trên Google Developers Console
  3. Tạo nhanh Project & khai báo thư viện Scribe Java API
  4. Code Project

1. Vấn đề với Google Oauth1a

Có một vấn đề với Google OAuth 1a. Bắt đầu từ ngày 20-04-2015, google chính thức không hỗ trợ giao thức OAuth 1 nữa. Nếu bạn đang sử dụng Scribe Java API để làm việc với Google OAuth 1.0, bạn bắt buộc phải thay đổi code của mình.

Trong tài liệu này tôi sẽ hướng dẫn bạn sử dụng Scribe Java API làm việc với Google OAuth 2.0

2. Tạo ứng dụng trên Google Developers Console

Trong lần đầu tiên, bạn chưa có một Project nào, bạn cần phải tạo nó, thông thường bạn nên đặt tên của Project theo tên của Website của bạn (Không bắt buộc).
Đặt tên project: ExampleWebsite
Project của bạn đã được tạo ra:
Vào mục "APIs and auth/Consent screen" để khai báo một Product Name.
Tạo mới Client ID:
Trong trường hợp bạn sử dụng OAuth2.0 trên ứng dụng web bạn phải tạo ClientID cho ứng dụng web. Đồng thời khai báo đường dẫn redirect (Đường dẫn sẽ redirect tới sau khi người dùng đăng nhập vào tài khoản google của họ và cho phép ứng dụng của bạn kết nối với Google OAuth).
ClientID đã được tạo ra, trong đó có 2 thông tin quan trọng nhất là Client IDClient Secret, bạn cần nó trong code Java (Sẽ được minh họa trong ví dụ dưới đây).

Chú ý: Bạn có thể tạo nhiều ClientID cho các ứng dụng khác nhau của bạn.

3. Tạo nhanh Project & khai báo thư viện Scribe Java API

  • File/New/Others
Project của bạn đã được tạo ra.
Cấu hình Maven sử dụng Scribe Java API:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                  http://maven.apache.org/xsd/maven-4.0.0.xsd">
                  
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.o7planning</groupId>
    <artifactId>ScribeGoogleOAuth2</artifactId>
    <version>0.0.1-SNAPSHOT</version>


    <dependencies>

        <!-- http://mvnrepository.com/artifact/org.scribe/scribe -->
        <dependency>
            <groupId>org.scribe</groupId>
            <artifactId>scribe</artifactId>
            <version>1.3.7</version>
        </dependency>

    </dependencies>
    
</project>

4. Code Project

Scribe Java API là một API giúp bạn làm việc với OAuth dễ dàng hơn, nó ẩn đi sự khác biệt giữa các nhà cung cấp dịch vụ OAuth (Google, yahoo, facebook,..), và hỗ trợ OAuth 1a, OAuth 2.0
Ví dụ để làm việc với LinkIn, code của bạn là:
OAuthService service = new ServiceBuilder()
                                 .provider(LinkedInApi.class)
                                 .apiKey(YOUR_API_KEY)
                                 .apiSecret(YOUR_API_SECRET)
                                 .build();
Để làm việc với Google OAuth 1a code của bạn là:
OAuthService service = new ServiceBuilder()
                                 .provider(GoogleApi.class)
                                 .apiKey(YOUR_API_KEY)
                                 .apiSecret(YOUR_API_SECRET)
                                 .build();
Scribe Java API đã có sẵn các API cho các nhà cung cấp khác nhau:
  • FacebookApi
  • GoogleApi
  • FoursquareApi
  • Foursquare2Api
  • YahooApi
  • TwitterApi
  • ....
Thật đáng tiếc là GooleApi chỉ sử dụng được cho Google OAuth 1, không sử dụng được cho Google OAuth 2.0, và bạn cũng không tìm thấy lớp Google2Api trong thư viện của Scribe 1.3.7 (Có thể vì lý do bản quyền).

Vì vậy trong project của mình bạn cần tạo class Google2Api, và chú ý, nó phải nằm trong package org.scribe.builder.api:
Google2Api.java
package org.scribe.builder.api;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.scribe.exceptions.OAuthException;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuth20ServiceImpl;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.OAuthEncoder;
import org.scribe.utils.Preconditions;

/**
* Google OAuth2.0 Released under the same license as scribe (MIT License)
*
* @author yincrash
*
*/
public class Google2Api extends DefaultApi20 {

    private static final String AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s";
    private static final String SCOPED_AUTHORIZE_URL = AUTHORIZE_URL
            + "&scope=%s";

    @Override
    public String getAccessTokenEndpoint() {
        return "https://accounts.google.com/o/oauth2/token";
    }

    @Override
    public AccessTokenExtractor getAccessTokenExtractor() {
        return new AccessTokenExtractor() {

            @Override
            public Token extract(String response) {
                Preconditions
                        .checkEmptyString(response,
                                "Response body is incorrect. Can't extract a token from an empty string");

                Matcher matcher = Pattern.compile(
                        "\"access_token\" : \"([^&\"]+)\"").matcher(response);
                if (matcher.find()) {
                    String token = OAuthEncoder.decode(matcher.group(1));
                    return new Token(token, "", response);
                } else {
                    throw new OAuthException(
                            "Response body is incorrect. Can't extract a token from this: '"
                                    + response + "'", null);
                }
            }
        };
    }

    @Override
    public String getAuthorizationUrl(OAuthConfig config) {
        // Append scope if present
        if (config.hasScope()) {
            return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()),
                    OAuthEncoder.encode(config.getScope()));
        } else {
            return String.format(AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()));
        }
    }

    @Override
    public Verb getAccessTokenVerb() {
        return Verb.POST;
    }

    @Override
    public OAuthService createService(OAuthConfig config) {
        return new GoogleOAuth2Service(this, config);
    }

    private class GoogleOAuth2Service extends OAuth20ServiceImpl {

        private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
        private static final String GRANT_TYPE = "grant_type";
        private DefaultApi20 api;
        private OAuthConfig config;

        public GoogleOAuth2Service(DefaultApi20 api, OAuthConfig config) {
            super(api, config);
            this.api = api;
            this.config = config;
        }

        @Override
        public Token getAccessToken(Token requestToken, Verifier verifier) {
            OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(),
                    api.getAccessTokenEndpoint());
            switch (api.getAccessTokenVerb()) {
            case POST:
                request.addBodyParameter(OAuthConstants.CLIENT_ID,
                        config.getApiKey());
                request.addBodyParameter(OAuthConstants.CLIENT_SECRET,
                        config.getApiSecret());
                request.addBodyParameter(OAuthConstants.CODE,
                        verifier.getValue());
                request.addBodyParameter(OAuthConstants.REDIRECT_URI,
                        config.getCallback());
                request.addBodyParameter(GRANT_TYPE,
                        GRANT_TYPE_AUTHORIZATION_CODE);
                break;
            case GET:
            default:
                request.addQuerystringParameter(OAuthConstants.CLIENT_ID,
                        config.getApiKey());
                request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET,
                        config.getApiSecret());
                request.addQuerystringParameter(OAuthConstants.CODE,
                        verifier.getValue());
                request.addQuerystringParameter(OAuthConstants.REDIRECT_URI,
                        config.getCallback());
                if (config.hasScope())
                    request.addQuerystringParameter(OAuthConstants.SCOPE,
                            config.getScope());
            }
            Response response = request.send();
            return api.getAccessTokenExtractor().extract(response.getBody());
        }
    }

}
Tạo một class MyConstants lưu trữ Google Client IDClient Secret mà bạn đã tạo ra trước đó trên Google Developers Console.
MyConstants.java
package org.o7planning.tutorial.scribegoogle;

public class MyConstants {

   // Client ID
   public static final String GOOGLE_CLIENT_ID = "884814088321-lfgc199ui38csrv75bf2fr3etksv0kpm.apps.googleusercontent.com";

   // Client Secret
   public static final String GOOGLE_CLIENT_SECRET = "yNHBZRDB-ThCIRa29pNJEyh-";

   // Redirect URI
   public static final String GOOGLE_REDIRECT_URL = "https://examplewebsite.com/mypath/oauth2callback";

}
Google2Example.java
package org.o7planning.tutorial.scribegoogle.test;

import java.util.Scanner;

import org.o7planning.tutorial.scribegoogle.MyConstants;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.Google2Api;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

public class Google2Example {

  private static final String NETWORK_NAME = "Google";

  private static final String PROTECTED_RESOURCE_URL = "https://www.googleapis.com/oauth2/v2/userinfo?alt=json";

  private static final String SCOPE = "https://mail.google.com/ https://www.googleapis.com/auth/userinfo.email";

  private static final Token EMPTY_TOKEN = null;

  public static void main(String[] args) {

      String apiKey = MyConstants.GOOGLE_CLIENT_ID;
      String apiSecret = MyConstants.GOOGLE_CLIENT_SECRET;
      String callbackUrl = MyConstants.GOOGLE_REDIRECT_URL;

      // Tạo OAuthService cho Google OAuth 2.0
      OAuthService service = new ServiceBuilder().provider(Google2Api.class)
              .apiKey(apiKey).apiSecret(apiSecret).callback(callbackUrl)
              .scope(SCOPE).build();

      Scanner in = new Scanner(System.in);

      System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ===");
      System.out.println();

      Verifier verifier = null;

      Token accessToken = null;

      // Obtain the Authorization URL
      System.out.println("Fetching the Authorization URL...");
      String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
      System.out.println("Got the Authorization URL!");
      System.out.println("Now go and authorize Scribe here:");
      System.out.println();

      // Copy URL này và chạy trên trình duyệt.
      System.out.println(authorizationUrl);
      System.out.println();

      // Copy Authorization Code trên URL của trình duyệt và dán vào Console.
      System.out.println("And paste the authorization code here");
      System.out.print(">>");
      verifier = new Verifier(in.nextLine());
      System.out.println();

      // Trade the Request Token and Verfier for the Access Token
      System.out.println("Trading the Request Token for an Access Token...");
      accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);
      System.out.println("Got the Access Token!");
      System.out.println("(if your curious it looks like this: "
              + accessToken + " )");
      System.out.println();

      // Now let's go and ask for a protected resource!
      System.out.println("Now we're going to access a protected resource...");
      OAuthRequest request = new OAuthRequest(Verb.GET,
              PROTECTED_RESOURCE_URL);
      service.signRequest(accessToken, request);
      Response response = request.send();
      System.out.println("Got it! Lets see what we found...");
      System.out.println();
      System.out.println(response.getCode());
      System.out.println(response.getBody());

      System.out.println();
      System.out
              .println("Thats it man! Go and build something awesome with Scribe! :)");
      in.close();
  }
}
Chạy lớp Google2Example:
Copy URL như hình khoanh đỏ phía trên và chạy trên trình duyệt:
Sau đăng nhập vào tài khoản google, Google đòi hỏi User cho phép ứng dụng xem một số thông tin tài khoản. Nhấn vào Accept để cho phép.
Website sẽ chuyển hướng (redirect) tới Callback-URL, bao gồm cả mã truy cập, bạn cần copy mã này:
Copy mã và dán lên cửa sổ Java Console:
Bạn sẽ nhận được các thông tin người dùng: