概览
目的:本文档介绍了如何使用 GoogleCredential 实用程序类对 Google 服务进行 OAuth 2.0 授权。如需了解我们提供的通用 OAuth 2.0 函数,请参阅 OAuth 2.0 和 Java 版 Google OAuth 客户端库。
摘要:如需访问 Google 服务中存储的受保护数据,请使用 OAuth 2.0 进行授权。Google API 支持不同类型的客户端应用的 OAuth 2.0 流程。在所有这些流程中,客户端应用会请求一个只与您的客户端应用和要访问的受保护数据的所有者相关联的访问令牌。访问令牌还与有限的范围相关联,该范围定义了客户端应用可以访问的数据类型(例如“管理您的任务”)。OAuth 2.0 的一个重要目标是提供对受保护数据的安全便捷访问,同时最大限度地降低访问令牌被盗的潜在影响。
Java 版 Google API 客户端库中的 OAuth 2.0 软件包是基于通用的 Java 版 Google OAuth 2.0 客户端库构建的。
如需了解详情,请参阅以下软件包的 Javadoc 文档:
- com.google.api.client.googleapis.auth.oauth2(来自 google-api-client)
- com.google.api.client.googleapis.extensions.appengine.auth.oauth2(来自 google-api-client-appengine)
Google API 控制台
无论客户端是已安装的应用、移动应用、网络服务器还是在浏览器中运行的客户端,在访问 Google API 之前,您都需要在 Google API 控制台中设置一个项目以用于身份验证和结算。
如需了解如何正确设置凭据,请参阅 API 控制台帮助。
凭据
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。与客户端应用请求访问最终用户数据的凭据不同,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(); }
数据存储区
访问令牌的有效期通常为 1 小时,之后,如果尝试使用该令牌,会收到错误消息。GoogleCredential 负责自动“刷新”令牌,即获取新的访问令牌。这可通过长期有效的刷新令牌来完成,如果您在授权代码流程中使用 access_type=offline
参数,该令牌通常会与访问令牌一起接收(请参阅 GoogleAuthorizationCodeFlow.Builder.setAccessType(String))。
大多数应用都需要保留该凭据的访问令牌和/或刷新令牌。如需保留凭据的访问令牌和/或刷新令牌,您可以使用 StoredCredential 提供自己的 DataStoreFactory 实现;也可以使用库提供的以下任一实现:
- AppEngineDataStoreFactory:使用 Google App Engine Data Store API 保留凭据。
- MemoryDataStoreFactory:在内存中“保留”凭据,凭据只能用作进程生命周期内的短期存储。
- FileDataStoreFactory:在文件中保留凭据。
AppEngine 用户:AppEngineCredentialStore 已被弃用,很快就会被移除。我们建议您将 AppEngineDataStoreFactory 与 StoredCredential 结合使用。如果您以旧方式存储了凭据,则可以使用添加的辅助方法 migrateTo(AppEngineDataStoreFactory) 或 migrateTo(DataStore) 进行迁移。
您可以使用 DataStoreCredentialRefreshListener,然后通过 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) 为凭据设置凭据。
授权代码流
使用授权代码流程允许最终用户向您的应用授予对其 Google API 上受保护数据的访问权限。此流程的协议在授权代码授权中指定。
此流程是使用 GoogleAuthorizationCodeFlow 实现的。具体步骤包括:
- 最终用户登录到您的应用。您需要将该用户与您的应用的唯一用户 ID 相关联。
- 根据用户 ID 调用 AuthorizationCodeFlow.loadCredential(String),以检查最终用户的凭据是否已知。如果是,就大功告成了。
- 如果未启用,请调用 AuthorizationCodeFlow.newAuthorizationUrl() 并将最终用户的浏览器定向到授权页面,以授权您的应用访问其受保护的数据。
- 然后,Google 授权服务器会将浏览器连同
code
查询参数一起重定向回应用指定的重定向网址。使用code
参数通过 AuthorizationCodeFlow.newTokenRequest(String) 请求访问令牌。 - 使用 AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) 存储并获取用于访问受保护资源的凭据。
或者,如果您不使用 GoogleAuthorizationCodeFlow,则可以使用较低级别的类:
- 使用 DataStore.get(String) 根据用户 ID 从商店加载凭据。
- 使用 GoogleAuthorizationCodeRequestUrl 将浏览器定向到授权页面。
- 使用 AuthorizationCodeResponseUrl 处理授权响应并解析授权代码。
- 使用 GoogleAuthorizationCodeTokenRequest 请求访问令牌,并可能请求刷新令牌。
- 创建新的 GoogleCredential 并使用 DataStore.set(String, V) 存储该凭据。
- 使用
GoogleCredential
访问受保护的资源。过期的访问令牌将自动使用刷新令牌进行刷新(如果适用)。确保使用 DataStoreCredentialRefreshListener,然后通过 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) 为凭据设置凭据。
在 Google API 控制台中设置项目时,根据您使用的流程,您可以从不同的凭据中进行选择。如需了解详情,请参阅设置 OAuth 2.0 和 OAuth 2.0 场景。每个流程的代码段如下。
Web 服务器应用
使用 OAuth 2.0 处理网络服务器应用中介绍了此流程的协议。
此库提供 servlet 辅助程序类,可显著简化基本用例的授权代码流程。您只需提供 AbstractAuthorizationCodeServlet 和 AbstractAuthorizationCodeCallbackServlet(来自 google-oauth-client-servlet)的具体子类,并将它们添加到 web.xml 文件中。请注意,您仍然需要处理 Web 应用的用户登录并提取用户 ID。
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 授权代码流几乎完全相同,区别在于我们可以利用 Google App Engine 的 Users Java API。用户需要登录才能启用用户 Java API;如需详细了解如何将用户重定向到登录页面,如果尚未登录,请参阅安全和身份验证(在 web.xml 中)。
与 servlet 案例的主要区别在于,您可以提供 AbstractAppEngineAuthorizationCodeServlet 和 AbstractAppEngineAuthorizationCodeCallbackServlet(来自 google-oauth-client-appengine)的具体子类。它们会扩展抽象 servlet 类,并使用 Users Java API 为您实现 getUserId
方法。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 还支持服务帐号。与客户端应用请求访问最终用户数据的凭据不同,服务帐号提供对客户端应用自有数据的访问权限。您的客户端应用使用从 Google API 控制台下载的私钥对访问令牌的请求进行签名。
从 plus-serviceaccount-cmdline-sample 获取的示例代码:
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); 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 中所述的命令行授权代码流。
plus-cmdline-sample 中的代码段示例:
public static void main(String[] args) { try { httpTransport = GoogleNetHttpTransport.newTrustedTransport(); 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 将浏览器中的最终用户重定向到授权页面,以授予浏览器应用访问最终用户受保护的数据的权限。
- 使用 JavaScript 版 Google API 客户端库处理在 Google API 控制台注册的重定向 URI 的网址片段中找到的访问令牌。
Web 应用的用法示例:
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 搭配使用的库:
如果您正在针对 Android 开发应用,并且您要使用的 Google API 包含在 Google Play 服务库中,请使用该库实现最佳性能和体验。如果您要在 Android 设备上使用的 Google API 不属于 Google Play 服务库,则可使用 Java 版 Google API 客户端库。该库支持 Android 4.0 (Ice Cream Sandwich)(或更高版本),详见下文。Java 版 Google API 客户端库支持 @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
参数。例如,“管理您的任务”就是上述 authtokenType
示例的别名。
您还必须从 Google 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; } }