概览
用途:本文档介绍了 适用于 Java 的 Google OAuth 客户端库。您可以将这些函数用于 身份验证和授权。
有关使用 GoogleCredential
进行 OAuth 2.0 授权的说明,
Google 服务,请参阅
将 OAuth 2.0 与适用于 Java 的 Google API 客户端库结合使用。
摘要:OAuth 2.0是一种 允许最终用户安全地对客户端进行授权的标准规范 访问受保护的服务器端资源。此外, OAuth 2.0 不记名令牌 规范说明了如何使用 令牌。
如需了解详情,请参阅以下软件包的 Javadoc 文档:
- com.google.api.client.auth.oauth2 (来自 google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (from google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
客户注册
在使用适用于 Java 的 Google OAuth 客户端库之前,您可能需要先 向授权服务器注册您的应用,以接收客户端 ID 并 客户端密钥。(有关此过程的一般信息,请参阅 客户 注册规范。)
凭据和凭据存储区
凭据
是一个线程安全的 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 HTTP 客户端库。
您可以使用库提供的以下实现之一:
- JdoDataStoreFactory 使用 JDO 保留凭据。
- AppEngineDataStoreFactory 使用 Google App Engine Data Store API 保留凭据。
- MemoryDataStoreFactory “永久”凭据存储在内存中,这仅作为短期用途 整个进程的生命周期
- FileDataStoreFactory 会将凭据保留在文件中
Google App Engine 用户:
AppEngineCredentialStore 已弃用,即将被移除。
我们建议您使用 AppEngineDataStoreFactory 与 StoredCredential 搭配使用。 如果您以旧方式存储凭据,则可以使用新增的辅助方法 migrateTo(AppEngineDataStoreFactory) 或 migrateTo(DataStore) 进行迁移
使用 DataStoreCredentialRefreshListener 并使用 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
授权代码流程
使用授权代码流程,让最终用户能够向您的应用授权 访问其受保护的数据。该流程的协议在 授权代码授权规范。
该流程是使用 AuthorizationCodeFlow。 具体步骤包括:
- 最终用户登录您的应用。您需要将该用户 您的应用的唯一用户 ID。
- 致电 AuthorizationCodeFlow.loadCredential(String), 检查用户的凭据是否已知。 如果是这样,您就大功告成了。
- 如果没有,请调用 AuthorizationCodeFlow.newAuthorizationUrl() 并将最终用户的浏览器定向到授权页面 您的应用对受保护数据的访问权限。
- 然后,网络浏览器会重定向到包含“代码”的重定向网址查询 参数,该参数可用于请求访问令牌 AuthorizationCodeFlow.newTokenRequest(String)。
- 使用 AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) 来存储并获取用于访问受保护资源的凭据。
或者,如果您不使用 AuthorizationCodeFlow、 您可以使用较低级别的类:
- 使用 DataStore.get(String) 根据用户 ID 从存储区加载凭据。
- 使用 AuthorizationCodeRequestUrl 将浏览器定向到授权页面。
- 使用 AuthorizationCodeResponseUrl 以处理授权响应并解析授权代码。
- 使用 AuthorizationCodeTokenRequest 来请求访问令牌,还可能需要请求刷新令牌。
- 创建一个新的 Credential,并使用 DataStore.set(String, V) 存储该凭据。
- 使用 Credential 访问受保护的资源。 在以下情况下,系统会使用刷新令牌自动刷新过期的访问令牌: 。请务必使用 DataStoreCredentialRefreshListener 并使用 Credential.Builder.addRefreshListener(CredentialRefreshListener).
Servlet 授权代码流程
此库提供 servlet 帮助程序类,以显著简化 基本用例的授权代码流程。您只需提供具体的子类 / AbstractAuthorizationCodeServlet 和 AbstractAuthorizationCodeCallbackServlet(来自 google-oauth-client-servlet) 并将其添加到您的 web.xml 文件中请注意,您仍然需要处理 登录并提取用户 ID。
示例代码:
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 几乎完全相同。 授权代码流程,只不过我们可以使用 Google App Engine 的 Users Java API。 用户需要登录才能启用 Users Java API;用于 有关如何将用户重定向到登录页面的信息 已登录,请参阅 安全和身份验证 (在 web.xml 中)。
与 servlet 方案的主要区别在于:您提供了
的子类
AbstractAppEngineAuthorizationCodeServlet 和 AbstractAppEngineAuthorizationCodeCallbackServlet(来自 google-oauth-client-appengine)。它们扩展了抽象 Servlet 类,并使用用户 Java API 为您实现 getUserId
方法。AppEngineDataStoreFactory(来自适用于 Java 的 Google HTTP 客户端库)是使用 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 应用处理在网址中找到的访问令牌 位于向授权服务器注册的重定向 URI 处的 fragment。
Web 应用的用法示例:
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
参数(位于
访问令牌响应。
该参数指定了已授予的访问令牌的生命周期(以秒为单位),
通常需要 1 小时。不过,访问令牌实际上可能不会
且服务器可能会继续允许访问。正因如此,
通常建议等待 401 Unauthorized
状态代码,而不是
并假设令牌已过期。或者,您也可以
尝试在访问令牌过期前不久刷新该令牌,并且如果令牌服务器
不可用,请继续使用访问令牌,直至收到 401
。这个
是 Google Cloud 中默认使用的
凭据。
另一种方法是在每次请求之前获取新的访问令牌, 每次都会向令牌服务器发送额外的 HTTP 请求,因此 速度和网络使用方面都很糟糕最好存储访问令牌 安全地永久存储应用,最大程度地减少应用对新访问的请求 词元。(但是对于已安装的应用程序而言,安全存储是一个难题。)
请注意,访问令牌可能会由于过期、 例如,如果用户已明确撤消令牌,因此请确保您的 非常稳健检测到令牌不再存在后 有效(例如,如果相应访问权限已过期或被撤消),那么您必须移除相应访问权限 存储令牌。例如,在 Android 上,您必须调用 AccountManager.invalidateAuthToken。