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

סקירה כללית

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

סיכום: כדי לגשת לנתונים מוגנים שמאוחסנים בשירותי Google, צריך להשתמש ב OAuth 2.0 להרשאה. ממשקי ה-API של Google תומכים בתהליכי 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

פרטי הכניסה החלופיים מבוססים על ממשק API של Google App Engine Identity Java. בשונה בפרטי הכניסה שבהם אפליקציית לקוח מבקשת גישה לנתוני משתמש קצה, ה-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.
  • MemoryDataStoreFactory: "persists" את פרטי הכניסה בזיכרון, שמועיל רק כאחסון לטווח קצר לכל משך התהליך.
  • FileDataStoreFactory: שומרת את פרטי הכניסה בקובץ.

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

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

תהליך הרשאה באמצעות קוד

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

התהליך הזה מוטמע באמצעות 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. המשתמש/ת צריכים להיות מחוברים לחשבון כדי להפעיל את Users Java API. אפשר למצוא מידע על מפנה משתמשים לדף התחברות אם הם עדיין לא מחוברים לחשבון, אבטחה ואימות (ב-web.xml).

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

הדוגמה צולמה (בשינוי קל) מ-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 Console.

הקוד לדוגמה נלקח מ-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 כדי לעבד את אסימון הגישה שנמצא במקטע כתובת האתר ב-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

@Beta

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

אם אתם מפתחים ל-Android וה-Google API שבו אתם רוצים להשתמש כלול ב ספריית Google Play Services, כדאי להשתמש בספרייה הזו כדי לקבל את הביצועים הטובים ביותר ואת החוויה הטובה ביותר. אם ממשק ה-API של Google שרוצים להשתמש בו עם Android הוא לא חלק מספריית Google Play Services, יכול להשתמש בספריית הלקוח של Google API עבור Java, התומכת ב-Android 4.0 (Ice Chromium סנדוויץ') (או גבוה יותר), וכפי שמתואר כאן. התמיכה ל-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. אחרת, האסימון שמנהל החשבון נותן לך מספק רק לך מכסה אנונימית, שבדרך כלל נמוכה מאוד. לעומת זאת, על ידי ציון ממשק 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;
  }
}