סקירה כללית
מטרה: במסמך הזה מתוארות הפונקציות הכלליות של OAuth 2.0 שמוצעות על ידי ספריית הלקוח של Google OAuth ל-Java. אפשר להשתמש בפונקציות האלה לאימות ולאישור של כל שירותי האינטרנט.
להוראות לשימוש ב-GoogleCredential
כדי לבצע הרשאה של OAuth 2.0 בשירותי Google, ראו שימוש ב-OAuth 2.0 עם ספריית הלקוח של Google API ל-Java.
סיכום: OAuth 2.0 הוא מפרט סטנדרטי שמאפשר למשתמשי קצה לאשר באופן מאובטח אפליקציית לקוח לגשת למשאבים מוגנים בצד השרת. בנוסף, במפרט של אסימון למוכ"ז (bearer) ב-OAuth 2.0 מוסבר איך לגשת למשאבים המוגנים האלה באמצעות אסימון גישה שניתן במהלך תהליך ההרשאה של משתמש הקצה.
פרטים נוספים זמינים במסמכי התיעוד של Javadoc לחבילות הבאות:
- com.google.api.client.auth.oauth2 (מ-google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (מ-google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
הרשמת לקוחות
לפני שתשתמשו בספריית הלקוח של Google OAuth ל-Java, סביר להניח שתצטרכו לרשום את האפליקציה בשרת הרשאות כדי לקבל מזהה לקוח וסוד לקוח. (מידע כללי על התהליך זמין במפרט של רישום הלקוח).
פרטי כניסה ואחסון פרטי כניסה
Credential היא כיתת עזר של OAuth 2.0 ללא סיכון לשרשור (thread-safe) שמאפשרת לגשת למשאבים מוגנים באמצעות אסימון גישה. כשמשתמשים באסימון רענון, 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 "שומר" את פרטי הכניסה בזיכרון, והם שימושיים רק כאחסון לטווח קצר למשך כל משך החיים של התהליך.
- FileDataStoreFactory שומר את פרטי הכניסה בקובץ.
משתמשי Google App Engine:
AppEngineCredentialStore הוצא משימוש ויוסר.
מומלץ להשתמש ב-AppEngineDataStoreFactory עם StoredCredential. אם פרטי הכניסה שמורים בדרך הישנה, תוכלו להשתמש בשיטות העזר שנוספו: migrateTo(AppEngineDataStoreFactory) או migrateTo(DataStore) כדי לבצע את ההעברה.
משתמשים ב-DataStoreCredentialRefreshListener ומגדירים אותו לפרטי הכניסה באמצעות GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
תהליך קוד ההרשאה
משתמשים בתהליך של קוד ההרשאה כדי לאפשר למשתמש הקצה להעניק לאפליקציה גישה לנתונים המוגנים שלו. הפרוטוקול לתהליך הזה מפורט במפרט להקצאת קוד הרשאה.
התהליך הזה מיושם באמצעות AuthorizationCodeFlow. השלבים:
- משתמש קצה מתחבר לאפליקציה. צריך לשייך את המשתמש הזה למזהה משתמש ייחודי לאפליקציה.
- קוראים ל-AuthorizationCodeFlow.loadCredential(String), על סמך מזהה המשתמש, כדי לבדוק אם פרטי הכניסה של המשתמש כבר ידועים. אם כן, סיימתם.
- אם לא, צריך להפעיל את AuthorizationCodeFlow.newAuthorizationUrl() ולהפנות את הדפדפן של משתמש הקצה לדף הרשאה שבו הוא יוכל להעניק לאפליקציה גישה לנתונים המוגנים שלו.
- לאחר מכן דפדפן האינטרנט מפנה לכתובת ה-URL להפניה אוטומטית עם פרמטר שאילתה "code" (קוד), שאפשר להשתמש בו כדי לבקש אסימון גישה באמצעות AuthorizationCodeFlow.newTokenRequest(String).
- משתמשים ב-AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) כדי לאחסן ולקבל פרטי כניסה לגישה למשאבים מוגנים.
לחלופין, אם לא משתמשים ב-AuthorizationCodeFlow, אפשר להשתמש במחלקות ברמה נמוכה יותר:
- משתמשים ב-DataStore.get(String) כדי לטעון את פרטי הכניסה מהחנות, על סמך מזהה המשתמש.
- משתמשים ב-AuthorizationCodeRequestUrl כדי להפנות את הדפדפן לדף ההרשאה.
- משתמשים ב-AuthorizationCodeResponseUrl כדי לעבד את תגובת ההרשאה ולנתח את קוד ההרשאה.
- משתמשים ב-AuthorizationCodeTokenRequest כדי לבקש אסימון גישה ואולי גם אסימון רענון.
- יוצרים פרטי כניסה חדשים ושומרים אותם באמצעות DataStore.set(String, V).
- גישה למשאבים מוגנים באמצעות פרטי הכניסה. אם רלוונטי, אסימוני גישה שפג תוקפם מתעדכנים באופן אוטומטי באמצעות אסימון הרענון. חשוב להשתמש ב-DataStoreCredentialRefreshListener ולהגדיר אותו לפרטי הכניסה באמצעות Credential.Builder.addRefreshListener(CredentialRefreshListener).
תהליך הרשאה באמצעות קוד הרשאה של 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 של Google App Engine. כדי להפעיל את Users Java API, המשתמש צריך להיות מחובר. מידע על הפניה אוטומטית של משתמשים לדף התחברות אם הם עדיין לא מחוברים זמין במאמר אבטחה ואימות (ב-web.xml).
ההבדל העיקרי מהמקרה של ה-servlet הוא שצריך לספק תתי-כיתות קונקרטיות של AbstractAppEngineAuthorizationCodeServlet ושל AbstractAppEngineAuthorizationCodeCallbackServlet (מ-google-oauth-client-appengine). הם מרחיבים את כיתות ה-servlet המופשטות ומטמיעים את השיטה getUserId
בשבילכם באמצעות Users Java API. AppEngineDataStoreFactory (מ-Google HTTP Client Library for Java) היא אפשרות טובה לשמירת פרטי הכניסה באמצעות Google App Engine Data Store API.
קוד לדוגמה:
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 למוכ"ז, כשמתבצעת קריאה לשרת כדי לגשת למשאב מוגן עם אסימון גישה שפג תוקפו, בדרך כלל השרת משיב עם קוד סטטוס HTTP 401 Unauthorized
, כמו:
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
. זוהי השיטה שמוגדרת כברירת מחדל ב-Credential.
אפשרות אחרת היא לקבל אסימון גישה חדש לפני כל בקשה, אבל לשם כך צריך לשלוח בקשת HTTP נוספת לשרת האסימונים בכל פעם, ולכן סביר להניח שזו בחירה לא טובה מבחינת המהירות ושימוש ברשת. מומלץ לאחסן את אסימון הגישה באחסון מאובטח וקבוע כדי לצמצם את מספר הבקשות של האפליקציה לאסימוני גישה חדשים. (אבל לאפליקציות מותקנות, אחסון מאובטח הוא בעיה קשה).
חשוב לזכור שטוקן גישה עשוי להיות לא תקף מסיבות אחרות מלבד תפוגה, למשל אם המשתמש ביטל את הטוקן באופן מפורש. לכן, חשוב לוודא שקוד טיפול השגיאות שלכם חזק. אם זיהית שאסימון כבר לא תקף, למשל אם תוקף האסימון פג או שהוא בוטל, עליך להסיר את אסימון הגישה מהאחסון. לדוגמה, ב-Android, צריך לקרוא ל-AccountManager.invalidateAuthToken.