1. Introducción

Hola a todos y bienvenidos a un nuevo post!. En Spring Boot – OAuth Server vimos como crear un servidor oauth y cómo realizar la integración de los usuarios de éste de forma manual. Sin embargo SpringOAuth2 nos ofrece un mecanismo para realizar el proceso de autenticación así como la consumición de las apis de una forma fácil, rápida y sencilla.

Nos centraremos en los siguientes dos aspectos:

  • OAuth2RestOperations. Atacar desde nuestro cliente a las apis del servidor
  • Login. Delegar el proceso de login de nuestro cliente en el servidor OAuth

Os podéis descargar el código de ejemplo de mi GitHub aquí.

Tecnologías empleadas:

  • Java 8
  • Gradle 3.1
  • SpringBoot 1.5.2.RELEASE
  • Spring 4.3.7.RELEASE
  • SpringOAuth2 2.2.0.13.RELEASE

2. Configuración inicial

Añadimos las dependencias org.springframework.boot:spring-boot-starter-web, org.springframework.boot:spring-boot-starter-security, org.springframework.security.oauth:spring-security-oauth2 al fichero build.gradle

group 'com.jorgehernandezramirez.spring.springboot.oauth.client'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'maven'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'

dependencyManagement {
    imports {
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6'
    }
}

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.springboot.oauth.client.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.security.oauth:spring-security-oauth2")
}

El main que arranca SpringBoot

package com.jorgehernandezramirez.spring.springboot.oauth.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Arrancamos la aplicación en el puerto 8081 en el contextPath /client. Debemos además inhabilitar la seguridad básica de Spring Security.

server:
   port: 8081
   contextPath: /client

security:
   basic:
      enabled: false

3. OAuth2RestOperations

SpringOAuth2 nos provee de la clase OAuth2RestOperations para atacar a las apis del servidor de una manera transparente. La forma en la que se opera es a través de la obtención de un token de acceso válido. En caso de no disponer de uno, se iniciará el proceso de autenticación oauth en el servidor. Las posteriores llamadas a las apis se hará uso del token de acceso obtenido.

Configuración

Configuramos un objeto de la clase OAuth2RestOperations para que ataque el servidor oauth que configuramos en el post [PONER POST]

package com.jorgehernandezramirez.spring.springboot.oauth.client.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler;
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;

import javax.servlet.Filter;
import java.util.Arrays;
import java.util.List;

@Configuration
@EnableOAuth2Client
public class OAuthClientConfiguration {

    private static final String OAUTH_CLIENT = "oauth-client";

    private static final String SECRET = "secret";

    private static final String OAUTH_TOKEN_URI = "http://localhost:8080/oauth/token";

    private static final String OAUTH_AUTHORIZATION_URI = "http://localhost:8080/oauth/authorize";

    private static final List<String> SCOPES = Arrays.asList("read");

    @Autowired
    private OAuth2ClientContext oauth2ClientContext;

    public OAuthClientConfiguration(){
        //Para Spring
    }

    @Bean
    public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
        return new OAuth2RestTemplate(buildResourceForOAuthClient(), oauth2ClientContext);
    }

    private OAuth2ProtectedResourceDetails buildResourceForOAuthClient(){
        return buildResourceDetails(OAUTH_CLIENT, SECRET, OAUTH_TOKEN_URI, OAUTH_AUTHORIZATION_URI,
                SCOPES);
    }

    private OAuth2ProtectedResourceDetails buildResourceDetails(final String clientId, final String secret,
                                                                final String accessTokenUri, final String userAuthorizationUri,
                                                                final List<String> scopes) {
        final AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
        resource.setClientId(clientId);
        resource.setClientSecret(secret);
        resource.setAccessTokenUri(accessTokenUri);
        resource.setUserAuthorizationUri(userAuthorizationUri);
        resource.setScope(scopes);
        return resource;
    }
}

Atacamos a la api del servidor a través del objeto OAuth2RestOperations. En caso de no disponer de un token de acceso se iniciará de forma transparente el proceso de autenticación oauth. En sucesivas llamadas se reutilizará el token obtenido.

package com.jorgehernandezramirez.spring.springboot.oauth.client.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResourceController {

    @Autowired
    private OAuth2RestOperations oAuth2RestOperations;

    public ResourceController(){
        //Para Spring
    }

    @RequestMapping("/user")
    public String getUser() {
        return oAuth2RestOperations.getForObject("http://localhost:8080/user", String.class);
    }

    @RequestMapping("/private")
    public String getPrivateResource() {
        return oAuth2RestOperations.getForObject("http://localhost:8080/private", String.class);
    }
}

Probando nuestra aplicación

Atacamos a la url

http://localhost:8081/client/private

Se inicia el proceso de autenticación oauth ya que el objeto OAuth2RestOperations no dispone de un token de acceso válido

Después de realizar la aprobación de los permisos solicitados consumimos el controlador

4. Proceso de login delagado

En algunas ocasiones nos puede interesar añadir a nuestra aplicación un mecanismo de autenticación delegada de tal forma que es otra entidad quién realice la autenticación de los usuarios. La forma de realizar este proceso es a través del mecanismo de autenticación oauth.

Configuración

Creamos en nuestro servidor oauth el siguiente cliente

  • Nombre cliente: oauth-login-client
  • Uri redirección: http://localhost:8081/client/login

Para realizar el proceso de autenticación delegado vamos a crear un objeto de la clase OAuth2ClientAuthenticationProcessingFilter indicando el cliente oauth y la url en servidor que nos da la información del usuario con la que vamos a construir nuestro contexto de seguridad. Además especificamos la url a la que debemos redirigir (/loginok) cuando el proceso de autenticación haya terminado.

@Configuration
@EnableOAuth2Client
public class OAuthClientConfiguration {

    ...

    private static final String OAUTH_LOGIN_CLIENT = "oauth-login-client";

    private static final String LOGINOK_URI = "/loginok";

    private static final String LOGINKO_URI = "/loginko";

    private static final String USERINFO_URI = "http://localhost:8080/user";

    @Bean
    public Filter buildOAuth2Filter(){
        final OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter = new OAuth2ClientAuthenticationProcessingFilter("/login");
        final OAuth2RestTemplate template = new OAuth2RestTemplate(buildResourceForOAuthLoginClient(), oauth2ClientContext);
        final UserInfoTokenServices tokenServices = new UserInfoTokenServices(USERINFO_URI, OAUTH_LOGIN_CLIENT);
        oAuth2ClientAuthenticationProcessingFilter.setRestTemplate(template);
        tokenServices.setRestTemplate(template);
        oAuth2ClientAuthenticationProcessingFilter.setTokenServices(tokenServices);
        oAuth2ClientAuthenticationProcessingFilter.setAuthenticationSuccessHandler(new ForwardAuthenticationSuccessHandler(LOGINOK_URI));
        oAuth2ClientAuthenticationProcessingFilter.setAuthenticationFailureHandler(new ForwardAuthenticationFailureHandler(LOGINKO_URI));
        return oAuth2ClientAuthenticationProcessingFilter;
    }

    private OAuth2ProtectedResourceDetails buildResourceForOAuthLoginClient(){
        return buildResourceDetails(OAUTH_LOGIN_CLIENT, SECRET, OAUTH_TOKEN_URI, OAUTH_AUTHORIZATION_URI,
                SCOPES);
    }

Controlador LoginController

package com.jorgehernandezramirez.spring.springboot.oauth.client.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
public class LoginController {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    public LoginController(){
        //Para Spring
    }

    @RequestMapping("/loginok")
    public String doLoginOk() {
        LOGGER.info("Autentication -> {}", SecurityContextHolder.getContext().getAuthentication());
        return "loginok";
    }

    @RequestMapping("/loginko")
    public String doLoginKo() {
        return "loginko";
    }
}

Probando la aplicación

Atacamos la url http://localhost:8080/client/login e iniciamos el proceso de autenticación delegada.

Finalmente después de realizar la aprobación redirigimos a la página de /loginok