استفاده از OAuth 2.0 با Google API Client Library برای جاوا

نمای کلی

هدف: این سند نحوه استفاده از کلاس ابزار GoogleCredential را برای انجام احراز هویت OAuth 2.0 با سرویس‌های گوگل توضیح می‌دهد. برای اطلاعات بیشتر در مورد توابع عمومی OAuth 2.0 که ما ارائه می‌دهیم، به OAuth 2.0 و کتابخانه کلاینت OAuth گوگل برای جاوا مراجعه کنید.

خلاصه: برای دسترسی به داده‌های محافظت‌شده ذخیره‌شده در سرویس‌های گوگل، از OAuth 2.0 برای مجوزدهی استفاده کنید. APIهای گوگل از جریان‌های OAuth 2.0 برای انواع مختلف برنامه‌های کلاینت پشتیبانی می‌کنند. در تمام این جریان‌ها، برنامه کلاینت یک توکن دسترسی درخواست می‌کند که فقط با برنامه کلاینت شما و مالک داده‌های محافظت‌شده‌ای که به آنها دسترسی دارد، مرتبط است. توکن دسترسی همچنین با یک دامنه محدود مرتبط است که نوع داده‌هایی را که برنامه کلاینت شما به آنها دسترسی دارد تعریف می‌کند (برای مثال "مدیریت وظایف شما"). یکی از اهداف مهم OAuth 2.0 فراهم کردن دسترسی ایمن و راحت به داده‌های محافظت‌شده است، در حالی که در صورت سرقت توکن دسترسی، تأثیر بالقوه آن را به حداقل می‌رساند.

بسته‌های OAuth 2.0 در کتابخانه کلاینت API گوگل برای جاوا، بر اساس کتابخانه کلاینت عمومی Google OAuth 2.0 برای جاوا ساخته شده‌اند.

برای جزئیات بیشتر، به مستندات Javadoc برای بسته‌های زیر مراجعه کنید:

کنسول API گوگل

قبل از اینکه بتوانید به APIهای گوگل دسترسی پیدا کنید، باید یک پروژه را در کنسول API گوگل برای اهداف احراز هویت و صدور صورتحساب راه‌اندازی کنید، چه کلاینت شما یک برنامه نصب شده باشد، چه یک برنامه موبایل، یک وب سرور یا کلاینتی که در مرورگر اجرا می‌شود.

برای دستورالعمل‌های مربوط به تنظیم صحیح اعتبارنامه‌هایتان، به راهنمای کنسول API مراجعه کنید.

اعتبارنامه

اعتبارنامه گوگل

GoogleCredential یک کلاس کمکی thread-safe برای OAuth 2.0 است که برای دسترسی به منابع محافظت‌شده با استفاده از access token استفاده می‌شود. برای مثال، اگر از قبل access token دارید، می‌توانید به روش زیر درخواست دهید:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

هویت موتور برنامه گوگل

این اعتبارنامه جایگزین مبتنی بر API جاوای Google App Engine 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 : اعتبارنامه را در حافظه "ذخیره" می‌کند، که فقط به عنوان یک حافظه کوتاه مدت برای طول عمر فرآیند مفید است.
  • FileDataStoreFactory : اعتبارنامه را در یک فایل حفظ می‌کند.

کاربران AppEngine: AppEngineCredentialStore منسوخ شده و به زودی حذف خواهد شد. توصیه می‌کنیم از AppEngineDataStoreFactory به همراه StoredCredential استفاده کنید. اگر اعتبارنامه‌ها را به روش قدیمی ذخیره کرده‌اید، می‌توانید از متدهای کمکی اضافه شده migrateTo(AppEngineDataStoreFactory) یا migrateTo(DataStore) برای انجام مهاجرت استفاده کنید.

می‌توانید از DataStoreCredentialRefreshListener استفاده کنید و آن را با استفاده از GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener ) برای اعتبارنامه تنظیم کنید.

جریان کد مجوز

از جریان کد مجوز استفاده کنید تا به کاربر نهایی اجازه دهید به برنامه شما دسترسی به داده‌های محافظت‌شده‌اش در APIهای گوگل را اعطا کند. پروتکل این جریان در اعطای کد مجوز مشخص شده است.

این جریان با استفاده از GoogleAuthorizationCodeFlow پیاده‌سازی شده است. مراحل آن عبارتند از:

  • کاربر نهایی وارد برنامه شما می‌شود. شما باید آن کاربر را با یک شناسه کاربری منحصر به فرد برای برنامه خود مرتبط کنید.
  • بر اساس شناسه کاربر، تابع AuthorizationCodeFlow.loadCredential(String) ) را فراخوانی کنید تا بررسی کنید که آیا اطلاعات احراز هویت کاربر نهایی از قبل شناخته شده است یا خیر. اگر چنین است، کار ما تمام است.
  • اگر اینطور نیست، تابع AuthorizationCodeFlow.newAuthorizationUrl() را فراخوانی کنید و مرورگر کاربر نهایی را به یک صفحه مجوز هدایت کنید تا به برنامه شما اجازه دسترسی به داده‌های محافظت‌شده‌اش داده شود.
  • سپس سرور احراز هویت گوگل، مرورگر را به همراه یک پارامتر کوئری code ، به آدرس اینترنتی (URL) هدایت می‌کند. از پارامتر code برای درخواست توکن دسترسی با استفاده از AuthorizationCodeFlow.newTokenRequest(String) ) استفاده کنید.
  • از AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) ) برای ذخیره و دریافت اعتبارنامه جهت دسترسی به منابع محافظت‌شده استفاده کنید.

از طرف دیگر، اگر از GoogleAuthorizationCodeFlow استفاده نمی‌کنید، می‌توانید از کلاس‌های سطح پایین‌تر استفاده کنید:

وقتی پروژه خود را در کنسول 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
  }
}

برنامه‌های کاربردی موتور اپلیکیشن گوگل

جریان کد مجوزدهی در App Engine تقریباً مشابه جریان کد مجوزدهی servlet است، با این تفاوت که می‌توانیم از API Users Java در Google App Engine استفاده کنیم. برای فعال شدن API Users Java، کاربر باید وارد سیستم شود؛ برای اطلاعات بیشتر در مورد هدایت کاربران به صفحه ورود در صورتی که قبلاً وارد سیستم نشده‌اند، به Security and Authentication (در web.xml) مراجعه کنید.

تفاوت اصلی با مورد servlet این است که شما زیرکلاس‌های عینی AbstractAppEngineAuthorizationCodeServlet و AbstractAppEngineAuthorizationCodeCallbackServlet (از google-oauth-client-appengine) را ارائه می‌دهید. آن‌ها کلاس‌های servlet انتزاعی را بسط می‌دهند و متد getUserId با استفاده از Users Java API برای شما پیاده‌سازی می‌کنند. AppEngineDataStoreFactory (از google-http-client-appengine ) گزینه خوبی برای حفظ اعتبارنامه با استفاده از 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 همچنین از حساب‌های سرویس پشتیبانی می‌کند. برخلاف اعتبارنامه که در آن یک برنامه کلاینت درخواست دسترسی به داده‌های کاربر نهایی را دارد، حساب‌های سرویس دسترسی به داده‌های خود برنامه کلاینت را فراهم می‌کنند. برنامه کلاینت شما درخواست یک توکن دسترسی را با استفاده از یک کلید خصوصی که از کنسول API گوگل دانلود شده است، امضا می‌کند.

مثال استفاده:

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 برای برنامه‌های سمت کلاینت» توضیح داده شده است، معمولاً این مراحل را دنبال می‌کنید:

  1. کاربر نهایی را در مرورگر با استفاده از GoogleBrowserClientRequestUrl به صفحه مجوز هدایت کنید تا به برنامه مرورگر خود دسترسی به داده‌های محافظت‌شده کاربر نهایی را اعطا کنید.
  2. از کتابخانه کلاینت API گوگل برای جاوا اسکریپت برای پردازش توکن دسترسی موجود در قطعه URL در URI تغییر مسیر ثبت شده در کنسول API گوگل استفاده کنید.

نمونه استفاده برای یک برنامه وب:

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);
}

اندروید

@بتا

از کدام کتابخانه برای اندروید استفاده کنیم:

اگر در حال توسعه برای اندروید هستید و API گوگلی که می‌خواهید از آن استفاده کنید در کتابخانه سرویس‌های گوگل پلی گنجانده شده است، برای بهترین عملکرد و تجربه از آن کتابخانه استفاده کنید. اگر API گوگلی که می‌خواهید با اندروید استفاده کنید بخشی از کتابخانه سرویس‌های گوگل پلی نیست، می‌توانید از کتابخانه کلاینت API گوگل برای جاوا استفاده کنید که از اندروید ۴.۰ (بستنی حصیری) (یا بالاتر) پشتیبانی می‌کند و در اینجا توضیح داده شده است. پشتیبانی از اندروید در کتابخانه کلاینت API گوگل برای جاوا @Beta است.

پیشینه:

با شروع از Eclair (SDK 2.1)، حساب‌های کاربری در دستگاه اندروید با استفاده از Account Manager مدیریت می‌شوند. تمام مجوزهای برنامه‌های اندروید به صورت مرکزی توسط 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 خاصی دارند که آنها نیز کار می‌کنند. برای مثال، "مدیریت وظایف شما" یک نام مستعار برای مثال authtokenType نشان داده شده در بالا است.

شما همچنین باید کلید API را از کنسول API گوگل مشخص کنید. در غیر این صورت، توکنی که 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;
  }
}