שימוש ב-OAuth 2.0 עם ספריית הלקוח של Google API עבור Java

סקירה

המטרה: במסמך הזה מוסבר איך להשתמש במחלקת השירות GoogleCredential כדי לבצע הרשאת OAuth 2.0 בשירותי Google. למידע על הפונקציות הכלליות של OAuth 2.0 שאנחנו מספקים, ראו OAuth 2.0 וספריית הלקוח של Google OAuth ל-Java.

Summary (סיכום): כדי לגשת לנתונים מוגנים שמאוחסנים בשירותי Google, השתמשו ב-OAuth 2.0 לאישור. Google APIs תומכים בתהליכי OAuth 2.0 לסוגים שונים של אפליקציות לקוח. בכל השלבים האלה, אפליקציית הלקוח מבקשת אסימון גישה שמשויך רק לאפליקציית הלקוח ולבעלים של הנתונים המוגנים שהגישה אליהם מתבצעת. אסימון הגישה משויך גם להיקף מוגבל, שמגדיר את סוג הנתונים שאליהם לאפליקציית הלקוח יש גישה (לדוגמה, 'ניהול המשימות שלך'). אחת המטרות החשובות של OAuth 2.0 היא לספק גישה מאובטחת ונוחה לנתונים המוגנים, ולמזער את ההשפעה הפוטנציאלית במקרה של גניבת אסימון גישה.

חבילות OAuth 2.0 בספריית הלקוח של Google API עבור Java מבוססות על ספריית הלקוח של Google OAuth 2.0 ל-Java לשימוש כללי.

לפרטים, אפשר לעיין במסמכי Javadoc של החבילות הבאות:

Google API Console

כדי להשתמש ב-Google APIs, עליכם להגדיר פרויקט ב-Google API Console למטרות אימות וחיוב, בין אם הלקוח שלכם הוא אפליקציה מותקנת, אפליקציה לנייד, שרת אינטרנט או לקוח שפועל בדפדפן.

לקבלת הוראות להגדרה נכונה של פרטי הכניסה, קראו את העזרה של מסוף API.

פרטי כניסה

GoogleCredential

GoogleCredential הוא מחלקת מסייע בטוח לשרשורי של OAuth 2.0 לגישה למשאבים מוגנים באמצעות אסימון גישה. לדוגמה, אם כבר יש לכם אסימון גישה, תוכלו לשלוח בקשה בדרכים הבאות:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

זהות ב-Google App Engine

פרטי הכניסה החלופיים האלה מבוססים על Google App Engine App Identity Java API. בניגוד לפרטי הכניסה שבהם אפליקציית לקוח מבקשת גישה לנתונים של משתמש קצה, ה-App Identity API נותן גישה לנתונים של אפליקציית הלקוח עצמה.

משתמשים ב-AppIdentityCredential (מ-google-api-client-appengine). פרטי הכניסה האלה הרבה יותר פשוטים כי Google App Engine מטפל בכל הפרטים. עליכם לציין רק את היקף OAuth 2.0 שנחוץ לכם.

קוד לדוגמה שנלקח מ-urlshortener-robots-appengine-sample:

static Urlshortener newUrlshortener() {
  AppIdentityCredential credential =
      new AppIdentityCredential(
          Collections.singletonList(UrlshortenerScopes.URLSHORTENER));
  return new Urlshortener.Builder(new UrlFetchTransport(),
                                  GsonFactory.getDefaultInstance(),
                                  credential)
      .build();
}

מאגר נתונים

בדרך כלל תאריך התפוגה של אסימון גישה הוא שעה אחת, ולאחר מכן תוצג הודעת שגיאה אם תנסו להשתמש בו. GoogleCredential מטפל ב "רענון" אוטומטי של האסימון, כלומר, קבלת אסימון גישה חדש. כדי לעשות את זה, משתמשים באסימון רענון לטווח ארוך, שבדרך כלל מתקבל יחד עם אסימון הגישה, אם משתמשים בפרמטר access_type=offline בתהליך של קוד ההרשאה (מידע נוסף זמין במאמר GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

רוב האפליקציות יצטרכו לשמור על אסימון הגישה ו/או אסימון הרענון של פרטי הכניסה. כדי לשמור על הגישה ו/או אסימוני הרענון של פרטי הכניסה, תוכלו לספק הטמעה משלכם של DataStoreFactory עם StoredCredential או להשתמש באחד מההטמעות הבאות שסופקו על ידי הספרייה:

  • AppEngineDataStoreFactory: שומר את פרטי הכניסה באמצעות ממשק ה-API של Google App Engine Data Store.
  • MemoryDataStoreFactory: שומר את פרטי הכניסה בזיכרון, שימושיים רק כאחסון לטווח קצר לכל משך החיים של התהליך.
  • FileDataStoreFactory: שומר את פרטי הכניסה בקובץ.

AppEngine Users: AppEngineCredentialStore הוצא משימוש ויוסר בקרוב. אנחנו ממליצים להשתמש ב-AppEngineDataStoreFactory עם StoredCredential. אם יש לכם פרטי כניסה מאוחסנים בגרסה הישנה, אתם יכולים לבצע את ההעברה באמצעות שיטות העזר שנוספו migrateTo(AppEngineDataStoreFactory) או migrateTo(DataStore).

אפשר להשתמש ב-DataStoreCredentialRefreshListener ולהגדיר אותו עבור פרטי הכניסה באמצעות GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).

תהליך קוד ההרשאה

השתמשו בתהליך של קוד ההרשאה כדי לאפשר למשתמש הקצה להעניק לאפליקציה שלכם גישה לנתונים המוגנים שלו ב-Google APIs. הפרוטוקול לתהליך הזה מופיע במאמר Authorization Code Grant.

תהליך זה ממומש באמצעות GoogleAuthorizationCodeFlow. השלבים:

  • משתמשי הקצה נכנסים לאפליקציה שלכם. תצטרכו לשייך את המשתמש הזה למזהה משתמש ייחודי לאפליקציה שלכם.
  • קוראים ל-AuthorizationCodeFlow.loadCredential(String)) על סמך מזהה המשתמש כדי לבדוק אם פרטי הכניסה של משתמש הקצה כבר ידועים. אם כן, סיימנו.
  • אם לא, צריך לקרוא ל-AuthorizationCodeFlow.newAuthorizationUrl() ולהפנות את הדפדפן של משתמש הקצה לדף הרשאה כדי לתת לאפליקציה שלכם גישה לנתונים המוגנים שלהם.
  • לאחר מכן שרת ההרשאות של Google יפנה מחדש את הדפדפן בחזרה לכתובת ה-URL להפניה אוטומטית שצוינה על ידי האפליקציה, יחד עם פרמטר השאילתה code. משתמשים בפרמטר code כדי לבקש אסימון גישה באמצעות AuthorizationCodeFlow.newTokenRequest(String).
  • משתמשים ב-AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String)) כדי לאחסן ולקבל פרטי כניסה כדי לגשת למשאבים מוגנים.

לחלופין, אם אתם לא משתמשים ב-GoogleAuthorizationCodeFlow, אפשר להשתמש במחלקות ברמה נמוכה יותר:

כשמגדירים את הפרויקט במסוף Google API, צריך לבחור בין פרטי הכניסה השונים בהתאם לתהליך העבודה שבו אתם משתמשים. לפרטים נוספים קראו את המאמר הגדרת OAuth 2.0 ותרחישי OAuth 2.0. בהמשך מוצגים קטעי קוד לכל אחד מהתהליכים.

אפליקציות של שרת אינטרנט

הפרוטוקול לתהליך הזה מוסבר במאמר שימוש ב-OAuth 2.0 לאפליקציות אינטרנט.

הספרייה הזו מספקת מחלקות מסייעות ל-servlet, כדי לפשט באופן משמעותי את תהליך קוד ההרשאה בתרחישים בסיסיים לדוגמה. כל מה שצריך לעשות הוא לספק מחלקות משנה ספציפיות של AbstractAuthorizationCodeServlet ו-AbstractAuthorizationCodeCallbackServlet (מ-google-oauth-client-servlet) ולהוסיף אותן לקובץ ה-web.xml. שימו לב שעדיין צריך לטפל בכניסת המשתמש לאפליקציית האינטרנט ולחלץ מזהה משתמש.

public class CalendarServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance(),
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance()
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

אפליקציות של Google App Engine

תהליך קוד ההרשאה ב-App Engine כמעט זהה לתהליך של קוד ההרשאה של servlet, אבל אנחנו יכולים להשתמש ב-Users Java API של Google App Engine. המשתמשים צריכים להיות מחוברים כדי להפעיל את User Java API. למידע על הפניה אוטומטית של משתמשים לדף התחברות (אם הם עדיין לא מחוברים), קראו את הקטע אבטחה ואימות (ב-web.xml).

ההבדל העיקרי לעומת התרחיש של servlet הוא שאתם מספקים מחלקות משנה קונקרטיות של AbstractAppEngineAuthorizationCodeServlet ושל AbstractAppEngineAuthorizationCodeCallbackServlet (מ-google-oauth-client-appengine. הם מרחיבים את המחלקות המופשטות של ה-servlet, ומטמיעים את השיטה getUserId באמצעות ה-Users Java API. AppEngineDataStoreFactory (מ-google-http-client-appengine) הוא אפשרות טובה לשמירת פרטי הכניסה באמצעות ממשק ה-API של חנות הנתונים של Google App Engine.

דוגמה שנלקחה (השתנתה מעט) מ-calendar-appengine-sample:

public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

class Utils {
  static String getRedirectUri(HttpServletRequest req) {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  static GoogleAuthorizationCodeFlow newFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
        getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }
}

public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  private static final long serialVersionUID = 1L;

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
    resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>");
    resp.setStatus(200);
    resp.addHeader("Content-Type", "text/html");
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

דוגמה נוספת זמינה במאמר storage-serviceaccount-appengine-sample.

חשבונות שירות

ב-GoogleCredential יש תמיכה גם בחשבונות שירות. בניגוד לפרטי הכניסה שבהם אפליקציית לקוח מבקשת גישה לנתונים של משתמש קצה, חשבונות שירות מספקים גישה לנתונים של אפליקציית הלקוח עצמה. אפליקציית הלקוח שלכם חותמת על הבקשה לקבלת אסימון גישה באמצעות מפתח פרטי שהורד ממסוף Google API.

קוד לדוגמה שנלקח מ-plus-serviceaccount-cmdline-sample:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
...
// Build service account credential.

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(PlusScopes.PLUS_ME));
// Set up global Plus instance.
plus = new Plus.Builder(httpTransport, jsonFactory, credential)
    .setApplicationName(APPLICATION_NAME).build();
...

דוגמה נוספת זמינה במאמר storage-serviceaccount-cmdline-sample.

התחזות

תוכלו להשתמש בתהליך של חשבון השירות גם כדי להתחזות למשתמש בדומיין שבבעלותכם. תהליך זה דומה מאוד לתהליך של חשבון השירות שלמעלה, אבל אפשר גם לקרוא ל-GoogleCredential.Builder.setServiceAccountUser(String).

אפליקציות מותקנות

זהו התהליך של קוד ההרשאה בשורת הפקודה המתואר במאמר שימוש ב-OAuth 2.0 לאפליקציות מותקנות.

קטע קוד לדוגמה מ-plus-cmdline-sample:

public static void main(String[] args) {
  try {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
    // authorization
    Credential credential = authorize();
    // set up global Plus instance
    plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(
        APPLICATION_NAME).build();
   // ...
}

private static Credential authorize() throws Exception {
  // load client secrets
  GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
      new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json")));
  // set up authorization code flow
  GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
      httpTransport, JSON_FACTORY, clientSecrets,
      Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory(
      dataStoreFactory).build();
  // authorize
  return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}

אפליקציות בצד הלקוח

כדי להשתמש בתהליך הלקוח מבוסס-הדפדפן שמתואר במאמר שימוש ב-OAuth 2.0 לאפליקציות בצד הלקוח, בדרך כלל פועלים לפי השלבים הבאים:

  1. מפנים את משתמש הקצה בדפדפן לדף ההרשאה באמצעות GoogleBrowserClientRequestUrl כדי להעניק לאפליקציית הדפדפן גישה לנתונים המוגנים של משתמש הקצה.
  2. השתמשו בספריית הלקוח של Google API ל-JavaScript כדי לעבד את אסימון הגישה שנמצא בקטע של כתובת ה-URL ב-URI של ההפניה שרשום בGoogle API Console.

דוגמה לשימוש באפליקציית אינטרנט:

public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
  String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com",
      "https://oauth2.example.com/oauthcallback", Arrays.asList(
          "https://www.googleapis.com/auth/userinfo.email",
          "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build();
  response.sendRedirect(url);
}

Android

@בטא

באיזו ספרייה להשתמש ב-Android:

אם אתם מפתחים ל-Android ו-Google API שבו אתם רוצים להשתמש כלול בספרייה של Google Play Services, כדאי להשתמש בספרייה הזו כדי ליהנות מהחוויה הטובה ביותר ומהביצועים הטובים ביותר. אם ה-Google API שבו אתם רוצים להשתמש ב-Android לא חלק מספריית Google Play Services, תוכלו להשתמש בספריית הלקוח של Google API עבור Java, שתומכת ב-Android 4.0 (Ice Cream Andwich) (או גרסה חדשה יותר), שמתוארת כאן. התמיכה ל-Android בספריית הלקוח של Google API ל-Java היא @Beta.

רקע:

החל מ-Eclair (SDK 2.1), חשבונות משתמשים מנוהלים במכשיר Android באמצעות מנהל החשבונות. כל ההרשאות לאפליקציות ל-Android מנוהלות באופן מרוכז על ידי ה-SDK באמצעות AccountManager. מציינים את היקף OAuth 2.0 שנדרש לאפליקציה, ומחזירים אסימון גישה לשימוש.

היקף OAuth 2.0 מוגדר באמצעות הפרמטר authTokenType בתור oauth2: בצירוף ההיקף. לדוגמה:

oauth2:https://www.googleapis.com/auth/tasks

המדיניות הזו מציינת גישת קריאה/כתיבה לממשק ה-API של Google Tasks. אם יש לכם צורך בכמה היקפים של OAuth 2.0, תוכלו להשתמש ברשימה שמופרדת ברווחים.

לחלק מממשקי ה-API יש פרמטרים מיוחדים של authTokenType שפועלים גם הם. לדוגמה, "ניהול המשימות שלך" הוא כינוי לדוגמה עבור authtokenType שמוצגת למעלה.

בנוסף, עליכם לציין את מפתח ה-API מתוך Google API Console. אחרת, האסימון שה-AccountManager נותן לכם רק מספק לכם מכסה אנונימית, שלרוב נמוכה מאוד. לעומת זאת, כשמציינים מפתח API מקבלים מכסה גדולה יותר בחינם, ואפשר להגדיר חיוב לשימוש מעליה.

קטע קוד לדוגמה שנלקח מ-tasks-android-sample:

com.google.api.services.tasks.Tasks service;

@Override
public void onCreate(Bundle savedInstanceState) {
  credential =
      GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS));
  SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
  credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
  service =
      new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential)
          .setApplicationName("Google-TasksAndroidSample/1.0").build();
}

private void chooseAccount() {
  startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  switch (requestCode) {
    case REQUEST_GOOGLE_PLAY_SERVICES:
      if (resultCode == Activity.RESULT_OK) {
        haveGooglePlayServices();
      } else {
        checkGooglePlayServicesAvailable();
      }
      break;
    case REQUEST_AUTHORIZATION:
      if (resultCode == Activity.RESULT_OK) {
        AsyncLoadTasks.run(this);
      } else {
        chooseAccount();
      }
      break;
    case REQUEST_ACCOUNT_PICKER:
      if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
        String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
        if (accountName != null) {
          credential.setSelectedAccountName(accountName);
          SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = settings.edit();
          editor.putString(PREF_ACCOUNT_NAME, accountName);
          editor.commit();
          AsyncLoadTasks.run(this);
        }
      }
      break;
  }
}