סקירה כללית
מטרה: במאמר הזה מוסבר איך להשתמש בכיתת כלי העזר GoogleCredential כדי לבצע הרשאה של OAuth 2.0 בשירותי Google. מידע על פונקציות OAuth 2.0 כלליות שאנחנו מספקים זמין במאמר OAuth 2.0 וספריית הלקוח של Google OAuth ל-Java.
סיכום: כדי לגשת לנתונים מוגנים שמאוחסנים בשירותי Google, צריך להשתמש ב-OAuth 2.0 לצורך הרשאה. ממשקי Google API תומכים בתהליכי OAuth 2.0 לסוגים שונים של אפליקציות לקוח. בכל התהליכים האלה, אפליקציית הלקוח מבקשת אסימון גישה שמשויך רק לאפליקציית הלקוח ולבעלים של הנתונים המוגנים שאליהם מתבצעת הגישה. אסימון הגישה משויך גם להיקף מוגבל שמגדיר את סוג הנתונים שאפליקציית הלקוח יכולה לגשת אליהם (לדוגמה, 'ניהול המשימות שלך'). מטרה חשובה של OAuth 2.0 היא לספק גישה מאובטחת ונוחה לנתונים המוגנים, תוך מזעור ההשפעה הפוטנציאלית אם אסימון גישה נגנב.
חבילות OAuth 2.0 בספריית הלקוח של Google API ל-Java מבוססות על ספריית הלקוח של Google OAuth 2.0 ל-Java, שהיא ספרייה לשימוש כללי.
לפרטים נוספים, אפשר לעיין בתיעוד של Javadoc לגבי החבילות הבאות:
- com.google.api.client.googleapis.auth.oauth2 (מתוך google-api-client)
- com.google.api.client.googleapis.extensions.appengine.auth.oauth2 (from google-api-client-appengine)
Google API Console
לפני שתוכלו לגשת לממשקי Google API, תצטרכו להגדיר פרויקט ב-Google API Console למטרות אימות וחיוב, בין אם הלקוח שלכם הוא אפליקציה מותקנת, אפליקציה לנייד, שרת אינטרנט או לקוח שפועל בדפדפן.
הוראות להגדרה נכונה של פרטי הכניסה מופיעות בעזרה בנושא API Console.
פרטי כניסה
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. בניגוד לאישורים שבהם אפליקציית לקוח מבקשת גישה לנתוני משתמש קצה, ה-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: שומר את פרטי הכניסה באמצעות Google App Engine Data Store API.
- MemoryDataStoreFactory: מאחסן את פרטי הכניסה בזיכרון, וזה שימושי רק כאחסון לטווח קצר למשך חיי התהליך.
- FileDataStoreFactory: שומר את פרטי הכניסה בקובץ.
משתמשי AppEngine: AppEngineCredentialStore הוצא משימוש ויוסר בקרוב. מומלץ להשתמש ב-AppEngineDataStoreFactory עם StoredCredential. אם יש לכם פרטי כניסה ששמורים בפורמט הישן, תוכלו להשתמש בשיטות העזר החדשות migrateTo(AppEngineDataStoreFactory) או migrateTo(DataStore) כדי לבצע את ההעברה.
אפשר להשתמש ב-DataStoreCredentialRefreshListener ולהגדיר אותו לפרטי הכניסה באמצעות GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).
הרשאה באמצעות קוד
אתם יכולים להשתמש בתהליך של קוד הרשאה כדי לאפשר למשתמש הקצה להעניק לאפליקציה שלכם גישה לנתונים המוגנים שלו בממשקי Google API. הפרוטוקול של התהליך הזה מפורט במאמר Authorization Code Grant.
התהליך הזה מיושם באמצעות GoogleAuthorizationCodeFlow. השלבים:
- משתמש הקצה מתחבר לאפליקציה. תצטרכו לשייך את המשתמש למזהה משתמש ייחודי לאפליקציה שלכם.
- מתקשרים אל AuthorizationCodeFlow.loadCredential(String)) על סמך מזהה המשתמש כדי לבדוק אם פרטי הכניסה של משתמש הקצה כבר ידועים. אם כן, סיימנו.
- אם לא, מתקשרים אל AuthorizationCodeFlow.newAuthorizationUrl() ומפנים את הדפדפן של משתמש הקצה אל דף הרשאה כדי להעניק לאפליקציה גישה לנתונים המוגנים שלו.
- שרת ההרשאות של Google יפנה את הדפדפן בחזרה לכתובת ההפניה האוטומטית שצוינה באפליקציה, יחד עם פרמטר שאילתה
code. משתמשים בפרמטרcodeכדי לבקש אסימון גישה באמצעות AuthorizationCodeFlow.newTokenRequest(String)). - משתמשים ב-AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) כדי לאחסן פרטי כניסה ולקבל אותם לצורך גישה למשאבים מוגנים.
לחלופין, אם אתם לא משתמשים ב-GoogleAuthorizationCodeFlow, אתם יכולים להשתמש במחלקות ברמה נמוכה יותר:
- משתמשים ב-DataStore.get(String) כדי לטעון את פרטי הכניסה מהמאגר על סמך מזהה המשתמש.
- משתמשים ב-GoogleAuthorizationCodeRequestUrl כדי להפנות את הדפדפן לדף ההרשאות.
- משתמשים ב-AuthorizationCodeResponseUrl כדי לעבד את תגובת ההרשאה ולנתח את קוד ההרשאה.
- משתמשים ב-GoogleAuthorizationCodeTokenRequest כדי לבקש אסימון גישה ואולי גם אסימון רענון.
- יוצרים GoogleCredential חדש ומאחסנים אותו באמצעות DataStore.set(String, V).
- גישה למשאבים מוגנים באמצעות
GoogleCredential. אסימוני גישה שתוקפם פג ירועננו אוטומטית באמצעות אסימון הרענון (אם רלוונטי). חשוב להשתמש ב-DataStoreCredentialRefreshListener ולהגדיר אותו עבור פרטי הכניסה באמצעות GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).
כשמגדירים את הפרויקט ב-Google API Console, בוחרים מבין סוגים שונים של פרטי כניסה, בהתאם לתהליך שבו משתמשים. פרטים נוספים זמינים במאמרים הגדרת OAuth 2.0 ותרחישים של OAuth 2.0. בהמשך מופיעים קטעי קוד לכל אחד מהתהליכים.
אפליקציות בשרת האינטרנט
הפרוטוקול של התהליך הזה מוסבר במאמר שימוש ב-OAuth 2.0 לאפליקציות של שרת אינטרנט.
הספרייה הזו מספקת מחלקות עזר של סרוולטים כדי לפשט באופן משמעותי את תהליך קבלת קוד ההרשאה לתרחישי שימוש בסיסיים. פשוט מספקים מחלקות משנה קונקרטיות של 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. כדי להפעיל את Users Java API, המשתמש צריך להיות מחובר לחשבון. למידע על הפניית משתמשים לדף כניסה אם הם עדיין לא מחוברים, אפשר לעיין במאמר Security and Authentication (ב-web.xml).
ההבדל העיקרי מהמקרה של ה-servlet הוא שאתם מספקים מחלקות משנה קונקרטיות של AbstractAppEngineAuthorizationCodeServlet ו-AbstractAppEngineAuthorizationCodeCallbackServlet (מ-google-oauth-client-appengine).
הם מרחיבים את מחלקות ה-servlet המופשטות ומטמיעים את method getUserId בשבילכם באמצעות Users Java API. AppEngineDataStoreFactory
(from google-http-client-appengine)
is a good option for persisting the credential using the Google App Engine Data
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.
דוגמה לשימוש:
HttpTransport httpTransport = new NetHttpTransport(); 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 לאפליקציות מותקנות.
דוגמה לשימוש:
public static void main(String[] args) { try { httpTransport = new NetHttpTransport(); 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 לאפליקציות בצד הלקוח, בדרך כלל מבצעים את השלבים הבאים:
- מפנים את משתמש הקצה בדפדפן לדף ההרשאה באמצעות GoogleBrowserClientRequestUrl כדי להעניק לאפליקציית הדפדפן גישה לנתונים המוגנים של משתמש הקצה.
- משתמשים ב-Google API Client Library for 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:
אם אתם מפתחים לאנדרואיד וממשק Google API שבו אתם רוצים להשתמש כלול בספריית שירותי Google Play, מומלץ להשתמש בספרייה הזו כדי ליהנות מהביצועים ומחוויית השימוש הטובים ביותר. אם Google API שרוצים להשתמש בו עם Android לא נכלל בספריית Google Play Services, אפשר להשתמש בספריית הלקוח של Google API ל-Java, שתומכת ב-Android 4.0 (Ice Cream Sandwich) (או בגרסאות מתקדמות יותר), ומתוארת כאן. התמיכה ב-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
ההרשאה הזו מאפשרת גישת קריאה וכתיבה ל-Google Tasks API. אם אתם צריכים כמה היקפי הרשאות של OAuth 2.0, אתם יכולים להשתמש ברשימה מופרדת ברווחים.
יש ממשקי API עם פרמטרים מיוחדים authTokenType שגם פועלים. לדוגמה,
'Manage your tasks' הוא כינוי ל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; } }