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.
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
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