Spring Boot + Spring Security + React + Keycloak

Ahora mostraré como utilizar Spring Boot, Spring Security y Keycloak para tener recursos protegidos accediendo a ellos utilizando React 😉.

Configurar Keycloak

Configurar Spring Boot

Configurar React

 

  • Keycloak


Keycloak se puede obtener utilizando docker o desde su página como Standalone server distribution (previamente en este enlace había indicado como obtenerlo y lanzarlo). 
Luego de lanzarlo estará disponible localmente y procedemos a realizar su configuración (en mi caso visitando http://localhost:8282/):

Si es la primera vez configuramos el usuario administrador. Una vez ingresado entramos a la aplicación:

Seleccionamos Add realm y definimos su nombre, en este ejemplo es: sso


Luego vamos a Clients y presionamos Create para agregar dos clientes: cliente-uno para Spring Boot y cliente-dos para React, en ambos definimos su Root URL y Web Origins (NO olvidar presionar el boton save cuando realizamos modificaciones, web origins puede ser más estricto, es * para el ejemplo):



Luego creamos el cliente-dos en forma similar al cliente-uno pero especificando la URL en la que se encontrará React (no olvidar presionar save luego de realizar las modificaciones):

Ahora creamos un Role para el Realm sso (en este ejemplo es privilegiado):

Luego creamos un usuario (sebastian) y lo asociamos al rol que creamos:




  • Spring Boot

La primera clase es la configuración de Keycloak:
@Configuration
@EnableWebSecurity
public class KeycloakSecurity extends KeycloakWebSecurityConfigurerAdapter {

  @Autowired
  public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
    final var kap = keycloakAuthenticationProvider();
    kap.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
    auth.authenticationProvider(kap);
  }

  @Bean
  public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
    return new KeycloakSpringBootConfigResolver();
  }

  @Bean
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
    return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
  }

  @Override
  protected void configure(final HttpSecurity http) throws Exception {
    super.configure(http);
    http.cors().and().csrf().disable().authorizeRequests().antMatchers("/personas*")
        .hasRole("privilegiado").anyRequest().permitAll();
  }
}

En el método configure() especificamos que todos los request a /personas* tienen que tener el rol privilegiado.

Y la clase para lanzar Spring Boot:
@SpringBootApplication
@RequestMapping
@CrossOrigin(allowCredentials="true")
public class SbKeycloakApplication {

  @GetMapping(path = "/")
  public ResponseEntity saludo() {
    return ResponseEntity.ok(new Persona(3, "tres"));
  }

  @GetMapping(path = "/personas")
  public ResponseEntity> customers(final Principal principal, final Model model) {
    final var personas = new ArrayList();
    personas.add(new Persona(1, "uno"));
    personas.add(new Persona(2, "dos"));
    return ResponseEntity.ok(personas);
  }

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

}
Para no extender el ejemplo quedan los metodos de recursos en la misma clase:
  • request a / no requieren autorizacion
  • request a /personas requieren autorizacion
y las propiedades de configuracion:
keycloak.principal-attribute=preferred_username
keycloak.auth-server-url=http://localhost:8282/auth
keycloak.realm=sso
keycloak.resource=cliente-uno
keycloak.public-client=true


Si lanzamos la aplicación y accedemos al http://localhost:8080 obtenemos lo siguiente:

es de acceso libre. Ahora accedemos al http://localhost:8080/personas y no podemos acceder inmediatamente:


 tenemos que especificar el usuario y clave asociados al rol privilegiado y accedemos a los recursos:




Así tenemos nuestra aplicacion Spring Boot protegida con Keycloak.

  • React

El ultimo paso es crear la vista:
  • propagará las credenciales al backend (spring boot)
  • permitirá acceder al inicio (público) y a los recursos protegidos (keycloak)
Mostraré las partes relacionadas con la seguridad y todo el resto estará disponible en Github.

  • dentro de la carpeta public tiene que quedar un archivo keycloak.json con el siguiente contenido (que puede variar dependiendo de la instalación):
    {
      "realm": "sso",
      "auth-server-url": "http://localhost:8282/auth",
      "ssl-required": "external",
      "resource": "cliente-dos",
      "public-client": true,
      "confidential-port": 0,
      "enable-cors": true
    }
    
 el cual se obtiene desde keycloak (Clients/cliente-dos/installation/keycloak OIDC JSON):


  • Ahora creamos los metodos relacionados con obtener los datos desde el backend en el componente Protegido.js:
    componentDidMount() {
        const keycloak = Keycloak('/keycloak.json');
        keycloak.init({onLoad: 'login-required'}).then(authenticated => {
          this.setState({ keycloak: keycloak, authenticated: authenticated });
          this.cargarDatosProtegidos();
        })
      }
    
      authorizationHeader() {
        if(!this.state.keycloak) return {};
        return {
          headers: {
            "Authorization": "Bearer " + this.state.keycloak.token
          }
        };
      }
    
      cargarDatosProtegidos() {
        fetch('http://localhost:8080/personas', this.authorizationHeader())
        .then(response => response.json())
        .then(result => this.setState({datos: result}))
        .catch(error => console.log(error));
      }
    
      render() {
        if (this.state.keycloak) {
          if (this.state.authenticated) 
             return ...codigo-contenido-autorizado...; 
           else return ...codigo-error...
        }
        return ...codigo-no-autorizado...;
      }
    }
    
(en github el código esta completo)
  • cuando se carga el componente carga los datos de acceso a keycloak e intenta cargar los datos protegidos del backend, si no tiene las credenciales se mostrará la pagina de autenticación del keycloak, luego se agrega al header del request y se accede a los recursos protegidos:



  •  También está el componente Logout.js que realiza el logout en keycloak:
    logout() {
      this.props.history.push('/');
      this.props.keycloak.logout();
    }
    

Y eso es todo 😌, está keycloak configurado, recursos de spring boot protegidos y la vista con React hermosa 😖. Ahora podemos continuar creando microservicios seguros 😁.

Enlaces del código fuente:

2 comentarios:

  1. Tengo problemas al probar tu ejemplo a dia de hoy, cuando intento cerrar la sesion desde el boton de la app React, me da un mensaje de error: Invalid parameter: redirect_uri

    El login me funciona correctamente, y hace las redirecciones, pero ahora no se si es problema de configuración o de la url que llama react hacia keycloak

    ResponderBorrar
    Respuestas
    1. Actualización, si se actualiza la versión "keycloak-js": "19.0.2" funciona.

      Borrar