El protocolo OAuth nos proporciona una manera muy fiable de autenticación, así como la posibilidad de interconectar sistemas sin tener que ceder nuestras credenciales a un tercero.
Visual Studio Online es uno de los proveedores que lo soporta, aunque con ciertas peculiaridades. En este artículo veremos cómo podemos utilizar OAuth con Java para conectarnos y autenticarnos, así las peculiaridades de VSO.
Cómo funciona OAuth2.0
Oauth se basa en dos conjuntos de clave, conocidos como key y secret, que el cliente (Aplicación A) proporciona al sistema al que se quiere conectar (Aplicación B). Veamos un ejemplo del ciclo de vida de una petición OAuth:
- El usuario desde Aplicación A solicita autenticar con Aplicación B
- Aplicación A redirige a la página de Aplicación B con la Api_Key, la url de retorno y otros parámetros como parte de la redirección.
- El usuario introduce sus credenciales y autoriza (o no) la cesión de datos desde la Aplicación B a la Aplicación A.
- La Aplicación B redirige de vuelta a la Aplicación A con la url de retorno especificada anteriormente con un código de autenticación.
- De lado de servidor, la Aplicación A le envía a la Aplicación B el código de autenticación, el secret, y otros parámetros.
- La Aplicación B devuelve un Token a la Aplicación A que identifica al usuario y que se puede empezar a utilizar para realizar peticiones.
Cliente Oauth
Vamos a crear un ejemplo de Aplicación A, es decir, un cliente OAuth. Para ello utilizaremos scribe, un componente OAuth para Java muy sencillo y muy fácil de extender para adaptar a nuestras necesidades:
OAuthService service = new ServiceBuilder() .provider(LinkedInApi.class) .apiKey(YOUR_API_KEY) .apiSecret(YOUR_API_SECRET) .build();
A partir de ese código podemos generar la URL de redirección o capturar el Token. Como Visual Studio Online no forma parte de los proveedores por defecto, crearemos nuestro propio proveedor:
@Override public class VSOnlineApi extends DefaultApi20 { @Override public String getAccessTokenEndpoint() { return "https://app.vssps.visualstudio.com/oauth2/token"; } @Override public String getAuthorizationUrl(OAuthConfig oac) { return String.format("https://app.vssps.visualstudio.com/oauth2/authorize?mkt=es&client_id=%s&response_type=Assertion&state=sample&scope=vso.profile&redirect_uri=%s", oac.getApiKey(), oac.getCallback()); } @Override public Verb getAccessTokenVerb() { return Verb.POST; } @Override public OAuthService createService(OAuthConfig config) { return new VSOOauthService(this, config); } @Override public AccessTokenExtractor getAccessTokenExtractor() { return new VSOTokenExtractor(); } }
Este proveedor consta de dos urls, la de token y la de autorización, el verbo que utilizaremos para solicitar el access token, y dos componentes, un servicio que crearemos a continuación y el extractor para procesar el código resultado de la autenticación. Con este proveedor podemos generar la URL para realizar la redirección:
OAuthService service = new ServiceBuilder() .provider(VSOnlineApi.class) .apiKey("KEY") .apiSecret("SECRET) .callback("https://rlbisbe.dev:4567/callback") .signatureType(SignatureType.QueryString) .build();
Por otra parte, para poder hacer la llamada del lado de servidor y validar el token necesitaremos implementar el servicio VSOOauthService, ya que la implementación OAuth de Visual Studio Online tiene ciertas peculiaridades.
public class VSOOauthService extends OAuth20ServiceImpl { private final DefaultApi20 api; private final OAuthConfig config; public VSOOauthService(DefaultApi20 api, OAuthConfig config) { super(api, config); this.api = api; this.config = config; } @Override public Token getAccessToken(Token requestToken, Verifier verifier) { OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint()); request.addBodyParameter(VSOConstants.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); request.addBodyParameter(VSOConstants.GRANT_TYPE, "urn:ietf:params:oauth:grant-type:jwt-bearer"); request.addBodyParameter(VSOConstants.CLIENT_ASSERTION, config.getApiSecret()); request.addBodyParameter(VSOConstants.ASSERTION, verifier.getValue()); request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); request.addHeader("Content-type", "application/x-www-form-urlencoded"); config.log(request.getCompleteUrl()); Response response = request.send(); return api.getAccessTokenExtractor().extract(response.getBody()); } }
Esta clase nos permite construir una petición POST (que hemos visto antes en que nos permitirá validar el código que hemos recibido desde Visual Studio Online utilizando el secret de nuestra aplicación y las claves específicas que nos solicita la aplicación, enviar la petición y convertir el resultado en un Token para futuras peticiones.
Creando la web para conectarnos al servicio
Una vez que tenemos el servicio creado para obtener el token, vamos a crear una pequeña página para poder hacer el proceso completo. Para ello utilizaremos spark, un proyecto inspirado en Sinatra que nos permite crear aplicaciones web de una manera rápida y sencilla:
OAuthService service = new ServiceBuilder() .provider(VSOnlineApi.class) .apiKey("KEY") .apiSecret("SECRET") .callback("https://rlbisbe.dev:4567/callback") .signatureType(SignatureType.QueryString) .debugStream(System.out) .debug() .build(); get("/auth", (req, res) -> { res.redirect(service.getAuthorizationUrl(EMPTY_TOKEN)); return null; }); get("/callback", (req, res) -> { String code = req.queryParams("code"); Verifier verifier = new Verifier(code); Token token = service.getAccessToken(EMPTY_TOKEN, verifier); return token; });
Con el sódigo anterior podemos ver dos puntos de entrada, /auth, que realiza la redirección al servicio, y /callback, que recoge el resultado de la redirección, parsea el código del resultado, solicita el token utilizando el servicio que hemos creado anteriormente y muestra el token como resultado:
Token[CODIFICADOENHEXADECIMAL, ]
Consideraciones adicionales: SSL
Visual Studio Online requiere que la url de retorno esté bajo el protocolo SSL y no admite localhost, con lo cual para pruebas lo que podemos hacer es crear un certificado autofirmado utilizando la utilidad Keytool de Java:
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
Además deberemos importar dicha keytool dentro de nuestro proyecto para poder utilizar HTTPS:
SparkBase.setSecure("C:\\Users\\Roberto\\Documents\\NetBeansProjects\\OauthClientTest\\keystore.jks", "password", null, null);
Finalmente, como no admite localhost, una solución puede ser utilizar el nombre DNS de nuestro ordenador de pruebas, o bien mediante el fichero hosts crear una entrada para 127.0.0.1, en mi caso rlbisbe.dev
Documentación
- Ejemplo completo en github: https://github.com/rlbisbe/visualstudio-online-auth-java
- Autenticación OAuth con Visual Studio Online: http://www.visualstudio.com/en-us/integrate/get-started/get-started-auth-oauth2-vsi
- Generando certificados autofirmados con la herramienta keytool: https://www.sslshopper.com/article-how-to-create-a-self-signed-certificate-using-java-keytool.html
- Registrar nuestra aplicación con Visual Studio Online: https://app.vssps.visualstudio.com/app/register
- Proyecto Scribe: https://github.com/fernandezpablo85/scribe-java
- Proyecto Spark: http://sparkjava.com/
- En el blog de Dropbox: OAuth 2 the hard way: https://www.dropbox.com/developers/blog/52/dropbox-core-api-and-oauth-2-the-hard-way