OAuth 2.0 וספריית לקוח OAuth של Google עבור Java

סקירה כללית

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

להוראות על השימוש ב-GoogleCredential לביצוע הרשאת OAuth 2.0 עם שירותי Google, מידע נוסף שימוש ב-OAuth 2.0 עם ספריית הלקוח של Google API ל-Java.

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

לפרטים, עיינו בתיעוד של Javadoc בשביל החבילות הבאות:

הרשמת לקוחות

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

מאגר של פרטי כניסה ופרטי כניסה

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

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

רוב האפליקציות צריכות לשמור את אסימון הגישה של פרטי הכניסה אסימון רענון כדי למנוע הפניה מחדש בעתיד להרשאה בדפדפן. CredentialStore ההטמעה בספרייה הזו הוצאה משימוש ותוסר בעתיד גרסאות חדשות. החלופה היא להשתמש DataStoreFactory ו-DataStore ממשק עם StoredCredential, שמסופקים על ידי ספריית הלקוח של Google HTTP ל-Java.

אפשר להשתמש באחת מההטמעות הבאות בספרייה:

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

משתמשי Google App Engine:

AppEngineCredentialStore הוצא משימוש ומוסר.

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

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

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

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

התהליך הזה מוטמע באמצעות AuthorizationCodeFlow השלבים:

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

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

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

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

קוד לדוגמה:

public class ServletSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

public class ServletCallbackSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .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 עבור Java הוא אפשרות טובה לשמירת פרטי הכניסה באמצעות ממשק ה-API של חנות הנתונים של Google App Engine).

קוד לדוגמה:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

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

קוד דוגמה פשוט יותר נלקח מ- dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

תהליך עבודה של לקוח מבוסס דפדפן

אלה השלבים הטיפוסיים בתהליך הלקוח מבוסס הדפדפן, המפורטים מפרט הענקת גישה משתמעת:

  • באמצעות BrowserClientRequestUrl, להפנות את הדפדפן של משתמש הקצה לדף ההרשאה שבו משתמש הקצה יכול להעניק לאפליקציה גישה לנתונים המוגנים שלהם.
  • שימוש באפליקציית JavaScript כדי לעבד את אסימון הגישה שנמצא בכתובת ה-URL מקטע ב-URI להפניה מחדש הרשום בשרת ההרשאות.

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

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

איתור אסימון גישה שפג תוקפו

בהתאם למפרט למוכ"ז של OAuth 2.0, כשהשרת נקרא כדי לגשת למשאב מוגן שתוקפו פג בדרך כלל, השרת מגיב עם קוד הסטטוס 401 Unauthorized של HTTP כמו:

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

עם זאת, נראה שיש גמישות רבה במפרט. עבור פרטים, עיין בתיעוד של ספק OAuth 2.0.

דרך חלופית היא לבדוק את הפרמטר expires_in תגובה לאסימון גישה. מציין את משך החיים בשניות של אסימון הגישה המוענק, בדרך כלל שעה. עם זאת, יכול להיות שהתוקף של אסימון הגישה לא יפוג בפועל פרק הזמן הזה, והשרת עשוי להמשיך לאפשר גישה. לכן אנחנו בדרך כלל מומלץ להמתין לקוד הסטטוס 401 Unauthorized במקום בהנחה שתוקף האסימון פג לפי הזמן שחלף. לחלופין, אפשר לנסות לרענן אסימון גישה זמן קצר לפני שהתוקף שלו פג, ואם שרת האסימונים לא זמינה, ממשיכים להשתמש באסימון הגישה עד שתתקבל 401. הזה היא השיטה שמשמשת כברירת מחדל פרטי כניסה.

אפשרות אחרת היא להשתמש באסימון גישה חדש לפני כל בקשה, אבל דורשת בקשת HTTP נוספת לשרת האסימונים בכל פעם, אז סביר להניח היא בחירה לא טובה מבחינת המהירות והשימוש ברשת. באופן אידיאלי, כדאי לאחסן את אסימון הגישה באחסון מאובטח וקבוע כדי למזער את בקשות האפליקציה לגישה חדשה לאסימונים אישיים. (אבל אחסון מאובטח הוא בעיה קשה עבור יישומים מותקנים).

שימו לב שאסימון גישה עלול להפוך ללא תקף מסיבות אחרות, מלבד תפוגה, לדוגמה אם המשתמש ביטל את האסימון באופן מפורש, לכן עליך לוודא לטיפול בשגיאות בקוד הוא יציב. לאחר שמאתרים את האסימון שכבר לא תקפה, לדוגמה אם התוקף שלו פג או אם הגישה שלו בוטלה, צריך להסיר את הגישה מהאחסון שלכם. לדוגמה, ב-Android, צריך להתקשר AccountManager.invalidateAuthToken.