Descripción general
Propósito: En este documento, se describen las funciones genéricas de OAuth 2.0 que ofrece la biblioteca cliente de Google OAuth para Java. Puedes usar estas funciones para la autenticación y autorización de cualquier servicio de Internet.
Si deseas obtener instrucciones para usar GoogleCredential
a fin de realizar la autorización de OAuth 2.0 con los servicios de Google, consulta Usa OAuth 2.0 con la biblioteca cliente de la API de Google para Java.
Resumen: OAuth 2.0 es una especificación estándar que permite a los usuarios finales autorizar de forma segura una aplicación cliente para acceder a recursos protegidos del servidor. Además, en la especificación del token del portador de OAuth 2.0 se explica cómo acceder a esos recursos protegidos mediante un token de acceso otorgado durante el proceso de autorización del usuario final.
Para obtener más información, consulta la documentación de Javadoc para los siguientes paquetes:
- com.google.api.client.auth.oauth2 (de google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (de google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (de google-oauth-client-appengine)
Registro de clientes
Antes de usar la biblioteca cliente de Google OAuth para Java, es probable que debas registrar tu aplicación con un servidor de autorización a fin de recibir un ID de cliente y un secreto del cliente. (Para obtener información general sobre este proceso, consulta la especificación de registro de clientes).
Credenciales y almacenamiento de credenciales
Credential es una clase auxiliar de OAuth 2.0 segura para los subprocesos a fin de acceder a los recursos protegidos mediante un token de acceso. Cuando usas un token de actualización, Credential
también lo actualiza cuando este vence mediante el token de actualización. Por ejemplo, si ya tienes un token de acceso, puedes realizar una solicitud de la siguiente manera:
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(); }
La mayoría de las aplicaciones deben conservar el token de acceso y el token de actualización para evitar redireccionamientos futuros a la página de autorización en el navegador. La implementación de CredentialStore en esta biblioteca está obsoleta y se quitará en versiones futuras. La alternativa es usar las interfaces DataStoreFactory y DataStore con StoredCredential, que proporciona la biblioteca cliente HTTP de Google para Java.
Puedes usar una de las siguientes implementaciones que proporciona la biblioteca:
- JdoDataStoreFactory conserva la credencial mediante JDO.
- AppEngineDataStoreFactory conserva la credencial mediante la API de almacenamiento de datos de Google App Engine.
- MemoryDataStoreFactory almacena la credencial en la memoria, lo cual solo es útil como almacenamiento a corto plazo durante el ciclo de vida.
- FileDataStoreFactory conserva la credencial en un archivo.
Usuarios de Google App Engine:
AppEngineCredentialStore dejó de estar disponible y se quitará.
Te recomendamos que uses AppEngineDataStoreFactory con StoredCredential. Si tienes credenciales almacenadas de la manera anterior, puedes usar los métodos auxiliares agregados migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) para migrar.
Usa DataStoreCredentialRefreshListener y configúralo para la credencial mediante GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Flujo de código de autorización
Usa el flujo de código de autorización para permitir que el usuario final otorgue a tu aplicación acceso a sus datos protegidos. El protocolo para este flujo se especifica en la especificación de otorgamiento de código de autorización.
Este flujo se implementa mediante AuthorizationCodeFlow. Estos son los pasos:
- Un usuario final accede a tu aplicación. Debes asociar a ese usuario con un ID de usuario que sea único para tu aplicación.
- Llama a AuthorizationCodeFlow.loadCredential(String), según el ID del usuario, para verificar si ya se conocen las credenciales del usuario. Si es así, listo.
- De lo contrario, llama a AuthorizationCodeFlow.newAuthorizationUrl() y dirige el navegador del usuario final a una página de autorización en la que el usuario puede otorgar acceso a los datos protegidos de la aplicación.
- El navegador web redirecciona a la URL de redireccionamiento con un parámetro de consulta &code; que se puede usar para solicitar un token de acceso mediante AuthorizationCodeFlow.newTokenRequest(String).
- Usa AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) para almacenar y obtener una credencial a fin de acceder a los recursos protegidos.
De manera alternativa, si no usas AuthorizationCodeFlow, puedes usar las clases de nivel inferior:
- Usa DataStore.get(String) para cargar la credencial desde la tienda, según el ID del usuario.
- Usa AuthorizationCodeRequestUrl para dirigir al navegador a la página de autorización.
- Usa AuthorizationCodeResponseUrl para procesar la respuesta de autorización y analizar el código de autorización.
- Usa AuthorizationCodeTokenRequest para solicitar un token de acceso y, posiblemente, un token de actualización.
- Cree una Credential nueva y guárdela con DataStore.set(String, V).
- Accede a recursos protegidos con la Credencial. Los tokens de acceso vencidos se actualizan automáticamente con el token de actualización, si corresponde. Asegúrate de usar DataStoreCredentialRefreshListener y configurarlo para la credencial mediante Credential.Builder.addRefreshListener(CredentialRefreshListener).
Flujo de código de autorización de Servlet
Esta biblioteca proporciona clases auxiliares de servlet a fin de simplificar de manera significativa el flujo de código de autorización para casos de uso básicos. Solo debes proporcionar subclases concretas de AbstractAuthorizationCodeServlet y AbstractAuthorizationCodeCallbackServlet (de google-oauth-client-servlet) y agregarlas a tu archivo web.xml. Ten en cuenta que aún debes encargarte del acceso del usuario para tu aplicación web y extraer un ID del usuario.
Código de muestra:
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 } }
Flujo de código de autorización de Google App Engine
El flujo de código de autorización en App Engine es casi idéntico al flujo de código de autorización del servlet, excepto que podemos aprovechar la API de Java de usuarios de Google App Engine. El usuario debe acceder para que se habilite la API de usuarios de Java. Si deseas obtener más información sobre cómo redireccionar a los usuarios a una página de acceso si todavía no accedieron, consulta Seguridad y autenticación (en web.xml).
La diferencia principal con el caso de servlet es que proporcionas subclases concretas de AbstractAppEngineAuthorizationCodeServlet y AbstractAppEngineAuthorizationCodeCallbackServlet (de google-oauth-client-appengine). Extienden las clases abstractas de servlet y, luego, implementan el método getUserId
por ti con la API de Java de usuarios. AppEngineDataStoreFactory (de la biblioteca cliente HTTP de Google para Java es una buena opción para conservar la credencial mediante la API de almacén de datos de Google App Engine).
Código de muestra:
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(); } }
Flujo de código de autorización de la línea de comandos
Código de ejemplo simplificado tomado de 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); ... }
Flujo de cliente basado en el navegador
Estos son los pasos típicos del flujo de cliente basado en el navegador especificado en la especificación de Otorgamiento implícito:
- Con BrowserClientRequestUrl, redirecciona el navegador del usuario final a la página de autorización en la que el usuario final puede otorgar acceso a la aplicación a los datos protegidos.
- Usa una aplicación de JavaScript para procesar el token de acceso que se encuentra en el fragmento de URL en el URI de redireccionamiento registrado con el servidor de autorización.
Ejemplo de uso para una aplicación 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); }
Detecta un token de acceso vencido
Según la especificación del portador de OAuth 2.0, cuando se llama al servidor para acceder a un recurso protegido con un token de acceso vencido, el servidor suele responder con un código de estado HTTP 401 Unauthorized
como el siguiente:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Sin embargo, parece haber mucha flexibilidad en la especificación. Para obtener más información, consulta la documentación del proveedor de OAuth 2.0.
Un enfoque alternativo es verificar el parámetro expires_in
en la respuesta del token de acceso.
Esto especifica la duración en segundos del token de acceso otorgado, que suele ser una hora. Sin embargo, es posible que el token de acceso no venza al final de ese período y que el servidor continúe permitiendo el acceso. Por ese motivo, recomendamos esperar un código de estado 401 Unauthorized
, en lugar de asumir que el token caducó según el tiempo transcurrido. Como alternativa, puedes intentar actualizar un token de acceso poco antes de que venza y, si el servidor de tokens no está disponible, puedes seguir usando el token de acceso hasta que recibas un 401
. Esta es la estrategia que se usa de forma predeterminada en Credential.
Otra opción es tomar un token de acceso nuevo antes de cada solicitud, pero eso requiere una solicitud HTTP adicional al servidor de token cada vez, por lo que es probable que sea una mala elección en términos de velocidad y uso de red. Lo ideal es que almacenes el token de acceso en un almacenamiento seguro y persistente para minimizar las solicitudes de tokens de acceso nuevos que hace una aplicación. (Pero para las aplicaciones instaladas, el almacenamiento seguro es un problema difícil).
Ten en cuenta que un token de acceso puede dejar de ser válido por motivos distintos del vencimiento, por ejemplo, si el usuario revocó explícitamente el token, por lo que debes asegurarte de que tu código de manejo de errores sea sólido. Una vez que hayas detectado que un token ya no es válido, por ejemplo, si venció o se revocó, debes quitar el token de acceso de tu almacenamiento. En Android, por ejemplo, debes llamar a AccountManager.invalidateAuthToken.