OAuth 2.0 और Java के लिए Google OAuth क्लाइंट लाइब्रेरी

खास जानकारी

मकसद: इस दस्तावेज़ में, Java के लिए उपलब्ध Google OAuth क्लाइंट लाइब्रेरी से मिलने वाले सामान्य OAuth 2.0 फ़ंक्शन के बारे में बताया गया है. आप इन फ़ंक्शन का इस्तेमाल किसी भी इंटरनेट सेवा की पुष्टि और अनुमति देने के लिए कर सकते हैं.

Google की सेवाओं की मदद से OAuth 2.0 की मदद से अनुमति देने के लिए, GoogleCredential का इस्तेमाल करने के निर्देशों के लिए, Java के लिए Google API क्लाइंट लाइब्रेरी के साथ OAuth 2.0 का इस्तेमाल करना देखें.

खास जानकारी: OAuth 2.0 एक स्टैंडर्ड स्पेसिफ़िकेशन है. इसमें असली उपयोगकर्ताओं को, सुरक्षित सर्वर साइड संसाधनों को ऐक्सेस करने के लिए, क्लाइंट ऐप्लिकेशन को सुरक्षित तरीके से अनुमति देने की अनुमति दी जाती है. इसके अलावा, OAuth 2.0 बेयरर टोकन की खास जानकारी में बताया गया है कि असली उपयोगकर्ता की अनुमति देने की प्रोसेस के दौरान मिले ऐक्सेस टोकन का इस्तेमाल करके, उन सुरक्षित रिसॉर्स को कैसे ऐक्सेस किया जाए.

ज़्यादा जानकारी के लिए, इन पैकेज के लिए Javadoc के दस्तावेज़ देखें:

क्लाइंट रजिस्ट्रेशन

Java के लिए Google OAuth क्लाइंट लाइब्रेरी का इस्तेमाल करने से पहले, आपको क्लाइंट आईडी और क्लाइंट सीक्रेट पाने के लिए, अपने ऐप्लिकेशन को अनुमति देने वाले सर्वर के साथ रजिस्टर करना होगा. (इस प्रक्रिया के बारे में सामान्य जानकारी के लिए, क्लाइंट रजिस्ट्रेशन की खास बातें देखें.)

क्रेडेंशियल और क्रेडेंशियल स्टोर

क्रेडेंशियल, थ्रेड से सुरक्षित 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 से करें, जो Java के लिए Google एचटीटीपी क्लाइंट लाइब्रेरी से मिलता है.

लाइब्रेरी में उपलब्ध कराए गए इनमें से किसी एक तरीके का इस्तेमाल किया जा सकता है:

  • JdoDataStoreFactory जेडीओ का इस्तेमाल करके क्रेडेंशियल को बनाए रखती है.
  • AppEngineDataStoreFactory Google App Engine Data Store API का इस्तेमाल करके क्रेडेंशियल को बनाए रखता है.
  • MemoryDataStoreFactory मेमोरी में क्रेडेंशियल को "जारी" रखता है, जो सिर्फ़ कुछ समय के लिए स्टोरेज के तौर पर काम करता है.
  • FileDataStoreFactory फ़ाइल में क्रेडेंशियल को बनाए रखता है.

Google App Engine के उपयोगकर्ता:

AppEngineCredentialStore का अब इस्तेमाल नहीं किया जा सकता है और इसे हटाया जा रहा है.

हमारा सुझाव है कि आप AppEngineDataStoreFactory को StoredCredential के साथ इस्तेमाल करें. अगर आपके क्रेडेंशियल पुराने तरीके से सेव किए गए हैं, तो माइग्रेट करने के लिए, जोड़े गए हेल्पर तरीकों MigrateTo(AppEngineDataStoreFactory) या migrationTo(DataStore) का इस्तेमाल करें.

DataStoreCredentialRefreshListener का इस्तेमाल करें और इसे GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) का इस्तेमाल करके, क्रेडेंशियल के लिए सेट करें.

ऑथराइज़ेशन कोड का फ़्लो

असली उपयोगकर्ता को आपके ऐप्लिकेशन को उनके सुरक्षित डेटा का ऐक्सेस देने के लिए, ऑथराइज़ेशन कोड के फ़्लो का इस्तेमाल करें. इस फ़्लो के प्रोटोकॉल की जानकारी ऑथराइज़ेशन कोड ग्रांट की खास बातों में दी गई है.

इस फ़्लो को AuthorizationCodeFlow का इस्तेमाल करके लागू किया जाता है. चरण इस प्रकार हैं:

  • कोई अंतिम उपयोगकर्ता आपके ऐप्लिकेशन में लॉग इन करता है. आपको उस उपयोगकर्ता को एक ऐसे यूज़र आईडी से जोड़ना होगा जो आपके ऐप्लिकेशन के लिए बना हो.
  • यूज़र आईडी के आधार पर, AuthorizationCodeFlow.loadCredential(String) को कॉल करें, ताकि यह पता चल सके कि उपयोगकर्ता के क्रेडेंशियल पहले से मौजूद हैं या नहीं. अगर हां, तो हो गया.
  • अगर ऐसा नहीं है, तो AuthorizationCodeFlow.newAuthorizationUrl() को कॉल करें. साथ ही, असली उपयोगकर्ता के ब्राउज़र को अनुमति देने वाले ऐसे पेज पर भेजें जहां वे आपके ऐप्लिकेशन को अपने सुरक्षित डेटा का ऐक्सेस दे सकें.
  • इसके बाद, वेब ब्राउज़र "कोड" क्वेरी पैरामीटर के साथ दूसरे वेबलिंक पर भेज देता है. इसका इस्तेमाल AuthorizationCodeFlow.newTokenRequest(String) का इस्तेमाल करके, ऐक्सेस टोकन का अनुरोध करने के लिए किया जा सकता है.
  • सुरक्षित किए गए रिसॉर्स को ऐक्सेस करने के लिए, क्रेडेंशियल सेव और पाने के लिए ऑथराइज़ेशनCodeFlow.createAndStoreCredential(TokenResponse, String) का इस्तेमाल करें.

इसके अलावा, अगर AuthorizationCodeFlow का इस्तेमाल नहीं किया जा रहा है, तो कम लेवल वाली क्लास का इस्तेमाल किया जा सकता है:

  • स्टोर से क्रेडेंशियल लोड करने के लिए, यूज़र आईडी के आधार पर DataStore.get(String) का इस्तेमाल करें.
  • ब्राउज़र को अनुमति देने वाले पेज पर ले जाने के लिए, AuthorizationCodeRequestUrl का इस्तेमाल करें.
  • अनुमति देने के अनुरोध को प्रोसेस करने और ऑथराइज़ेशन कोड पार्स करने के लिए, AuthorizationCodeResponseUrl का इस्तेमाल करें.
  • ऐक्सेस टोकन या रीफ़्रेश टोकन का अनुरोध करने के लिए, AuthorizationCodeTokenRequest का इस्तेमाल करें.
  • एक नया क्रेडेंशियल बनाएं और उसे DataStore.set(String, V) का इस्तेमाल करके स्टोर करें.
  • क्रेडेंशियल का इस्तेमाल करके, सुरक्षित संसाधनों को ऐक्सेस करें. अगर लागू हो, तो रीफ़्रेश टोकन का इस्तेमाल करके, खत्म हो चुके ऐक्सेस टोकन अपने-आप रीफ़्रेश हो जाते हैं. पक्का करें कि DataStoreCredentialRefreshListener का इस्तेमाल हो. साथ ही, इसे Credential.Builder.addRefreshListener(CredentialRefreshListener) का इस्तेमाल करके, क्रेडेंशियल के लिए सेट करें.

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 पर ऑथराइज़ेशन कोड का फ़्लो, सर्वलेट के ऑथराइज़ेशन कोड फ़्लो के जैसा होता है. हालांकि, हम Google App Engine के Users Java API के फ़्लो का इस्तेमाल कर सकते हैं. उपयोगकर्ता Java API को चालू करने के लिए, उपयोगकर्ता को लॉग इन करना ज़रूरी है. अगर उपयोगकर्ताओं ने पहले से लॉग इन न किया हो, तो उन्हें लॉगिन पेज पर रीडायरेक्ट करने के बारे में जानकारी के लिए, सुरक्षा और पुष्टि करना (web.xml में) देखें.

सर्वेलेट केस और सर्वलेट केस के बीच मुख्य अंतर यह है कि आप (google-oauth-client-appengine से) AbstractAppEngineAuthorizationCodeServlet और AbstractAppEngineAuthorizationCodeCallbackServlet सब-क्लास उपलब्ध कराते हैं. वे ऐब्सट्रैक्ट सर्वलेट क्लास को बढ़ाते हैं और उपयोगकर्ता Java API का इस्तेमाल करके, आपके लिए getUserId तरीके को लागू करते हैं. AppEngineDataStoreFactory (Google App Engine Data Store API का इस्तेमाल करके, क्रेडेंशियल को बनाए रखने के लिए, जावा के लिए Google एचटीटीपी क्लाइंट लाइब्रेरी का इस्तेमाल करना अच्छा विकल्प है.

कोड का नमूना:

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 ऐप्लिकेशन का इस्तेमाल करें.

वेब ऐप्लिकेशन के लिए इस्तेमाल का सैंपल:

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/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 मिलने तक ऐक्सेस टोकन का इस्तेमाल जारी रखें. यह वह रणनीति है जिसे क्रेडेंशियल में, डिफ़ॉल्ट रूप से इस्तेमाल किया जाता है.

दूसरा विकल्प यह है कि हर अनुरोध से पहले एक नया ऐक्सेस टोकन लिया जाए. हालांकि, इसके लिए हर बार टोकन सर्वर के लिए एक अतिरिक्त एचटीटीपी अनुरोध की ज़रूरत होती है. इसलिए, स्पीड और नेटवर्क के इस्तेमाल के मामले में यह अच्छा विकल्प नहीं है. सही तरीका यह है कि ऐक्सेस टोकन को सुरक्षित और स्थायी स्टोरेज में सेव करें. इससे ऐप्लिकेशन के नए ऐक्सेस टोकन के लिए किए जाने वाले अनुरोधों को कम किया जा सकेगा. (लेकिन इंस्टॉल किए गए ऐप्लिकेशन के लिए, सुरक्षित मेमोरी एक कठिन समस्या है.)

ध्यान दें कि ऐक्सेस टोकन, समय-सीमा खत्म होने के अलावा अन्य वजहों से भी अमान्य हो सकता है. उदाहरण के लिए, अगर उपयोगकर्ता ने साफ़ तौर पर टोकन को निरस्त कर दिया है, तो पक्का करें कि गड़बड़ी को ठीक करने वाला आपका कोड मज़बूत हो. जब आपको पता चले कि कोई टोकन अब मान्य नहीं है. जैसे, अगर उसकी समयसीमा खत्म हो गई हो या उसे वापस ले लिया गया हो, तो आपको स्टोरेज से ऐक्सेस टोकन हटाना होगा. उदाहरण के लिए, Android पर आपको AccountManager.invalidateAuthToken को कॉल करना होगा.