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:
  1. @Configuration
  2. @EnableWebSecurity
  3. public class KeycloakSecurity extends KeycloakWebSecurityConfigurerAdapter {
  4. @Autowired
  5. public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
  6. final var kap = keycloakAuthenticationProvider();
  7. kap.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
  8. auth.authenticationProvider(kap);
  9. }
  10. @Bean
  11. public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
  12. return new KeycloakSpringBootConfigResolver();
  13. }
  14. @Bean
  15. @Override
  16. protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
  17. return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
  18. }
  19. @Override
  20. protected void configure(final HttpSecurity http) throws Exception {
  21. super.configure(http);
  22. http.cors().and().csrf().disable().authorizeRequests().antMatchers("/personas*")
  23. .hasRole("privilegiado").anyRequest().permitAll();
  24. }
  25. }
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:
  1. @SpringBootApplication
  2. @RequestMapping
  3. @CrossOrigin(allowCredentials="true")
  4. public class SbKeycloakApplication {
  5. @GetMapping(path = "/")
  6. public ResponseEntity saludo() {
  7. return ResponseEntity.ok(new Persona(3, "tres"));
  8. }
  9. @GetMapping(path = "/personas")
  10. public ResponseEntity> customers(final Principal principal, final Model model) {
  11. final var personas = new ArrayList();
  12. personas.add(new Persona(1, "uno"));
  13. personas.add(new Persona(2, "dos"));
  14. return ResponseEntity.ok(personas);
  15. }
  16. public static void main(final String[] args) {
  17. SpringApplication.run(SbKeycloakApplication.class, args);
  18. }
  19. }
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:
  1. keycloak.principal-attribute=preferred_username
  2. keycloak.auth-server-url=http://localhost:8282/auth
  3. keycloak.realm=sso
  4. keycloak.resource=cliente-uno
  5. 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):
    1. {
    2. "realm": "sso",
    3. "auth-server-url": "http://localhost:8282/auth",
    4. "ssl-required": "external",
    5. "resource": "cliente-dos",
    6. "public-client": true,
    7. "confidential-port": 0,
    8. "enable-cors": true
    9. }
 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:
    1. componentDidMount() {
    2. const keycloak = Keycloak('/keycloak.json');
    3. keycloak.init({onLoad: 'login-required'}).then(authenticated => {
    4. this.setState({ keycloak: keycloak, authenticated: authenticated });
    5. this.cargarDatosProtegidos();
    6. })
    7. }
    8. authorizationHeader() {
    9. if(!this.state.keycloak) return {};
    10. return {
    11. headers: {
    12. "Authorization": "Bearer " + this.state.keycloak.token
    13. }
    14. };
    15. }
    16. cargarDatosProtegidos() {
    17. fetch('http://localhost:8080/personas', this.authorizationHeader())
    18. .then(response => response.json())
    19. .then(result => this.setState({datos: result}))
    20. .catch(error => console.log(error));
    21. }
    22. render() {
    23. if (this.state.keycloak) {
    24. if (this.state.authenticated)
    25. return ...codigo-contenido-autorizado...;
    26. else return ...codigo-error...
    27. }
    28. return ...codigo-no-autorizado...;
    29. }
    30. }
(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:
    1. logout() {
    2. this.props.history.push('/');
    3. this.props.keycloak.logout();
    4. }

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: