Spring Security: Securización API REST con BASIC y base de datos

Última actualización: 21/08/2022
logo spring

En este tutorial veremos cómo utilizar Spring Security para configurar un sistema de autenticación y autorización que aplicaremos a una API REST, sacando partido a las facilidades que nos ofrece Spring Boot.

En concreto, leeremos las credenciales y permisos de los usuarios de una base de datos y los clientes de la API deberán proporcionar esas credenciales según el estándar BASIC. Es un mecanismo sencillo de implementar, lo que lo convierte en el ejemplo perfecto para dar los primeros pasos con Spring Security. Asimismo, en algunos escenarios, puede que sea suficiente.

Proyecto de ejemplo

El proyecto de ejemplo con el que vamos a trabajar es una API REST implementada con Spring Boot 2.7. Todos los conocimientos necesarios los explico en Introducción a Spring Boot: Aplicación con servicios REST y Spring Data JPA.

Cuanta con dos clases «controladoras». La primera de ellas ofrece las operaciones GET y DELETE para trabajar con el recurso Country. En ambos casos se debe proporcionar el identificador del país en la propia url.

package com.danielme.springsecuritybasic.controllers;

import com.danielme.springsecuritybasic.model.Country;
import com.danielme.springsecuritybasic.services.CountryService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(CountryRestController.COUNTRIES_RESOURCE)
public class CountryRestController {

    public static final String COUNTRY_RESOURCE = "/countries";
    public static final String COUNTRIES_ID_PATH = "/{id}";

    private final CountryService countryService;

    public CountryRestController(CountryService countryService) {
        this.countryService = countryService;
    }

    @GetMapping(value = COUNTRIES_ID_PATH)
    public ResponseEntity<Country> getById(@PathVariable("id") Long id) {
        return countryService.findById(id)
                .map(ResponseEntity::ok)
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    @DeleteMapping(value = COUNTRIES_ID_PATH)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteById(@PathVariable("id") Long id) {
        countryService.deleteById(id);
    }
    
}

El segundo controlador expone un GET que devuelve un status 204.

package com.danielme.springsecuritybasic.controllers;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(TestRestController.TEST_RESOURCE)
public class TestRestController {

    public static final String TEST_RESOURCE = "/test";

    @GetMapping
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void testEndpoint() {
        // nothing
    }
}

Esta es la lista de servicios.

La clase Main es la más simple posible.

package com.danielme.springsecuritybasic;

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

@SpringBootApplication
public class SpringBootApp {

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

}

Como raíz de todas las url de la aplicación, he añadido /api/ en el fichero application.properties

server.servlet.context-path=/api

Este es el pom, con cuatro starters: web, jdbc (base de datos), testing y, por supuesto, security.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath />
    </parent>
    <groupId>com.danielme</groupId>
    <artifactId>spring-security-rest-basic</artifactId>
    <version>1.0.0</version>
    <name>spring-security-basic-rest</name>
    <description>Demo project for Spring REST API with Spring Security - BASIC + JDBC</description>
    <url>https://danielme.com/2019/03/19/spring-rest-basic-spring-security/</url>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>     

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Base de datos

La base de datos precisa especial atención. Voy a optar por la estrategia más simple: una base de datos en memoria llamada HyperSQL integrable con Spring Boot. La incluimos en el pom.

 <dependency>
     <groupId>org.hsqldb</groupId>
     <artifactId>hsqldb</artifactId>
     <scope>runtime</scope>
 </dependency>

No hay que configurar los datos de conexión. Spring Boot ya sabe lo que tiene que hacer.

En los ficheros /src/main/resources/schema.sql y /src/main/resources/data.sql se encuentra el script de inicio. Spring Boot lo ejecutará cada vez que arranque. Téngase en cuenta que la base de datos es volátil y cualquier cambio realizado se perderá cuando la aplicación se detenga.

CREATE TABLE users
(
    id       INTEGER      NOT NULL,
    enabled  BOOLEAN      NOT NULL,
    name     VARCHAR(20)  NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    rol      VARCHAR(10)  NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO users (id, enabled, name, password, rol)
VALUES (1, true, 'admin', '$2a$10$tnC4pYqrAwCnDCFkFbjxV.PDE/b.fKI0aygmMQO0vKx5dki7WFT46', 'ROLE_ADMIN'),
       (2, true, 'user', '$2a$10$PR4ElawJcWhuLoBPnP4CDeG1c0NSyGPteTq9AYcbDl8vB8sMZ/C4K', 'ROLE_USER');

Vemos que el script anterior crea una tabla para los usuarios. Almacena el nombre, la contraseña, el rol asignado (por simplicidad solo uno) el cual debe comenzar por el prefijo «ROLE_» y un lógico que indica si el usuario está activo. Tenemos dos usuarios activos de credenciales user\user y admin\admin.

Pero, un momento, ¿por qué la contraseña es una ristra larga e incomprensible de caracteres? Se trata de una medida de seguridad fundamental: las contraseñas JAMÁS se deben guardar tal cual. Las estoy cifrando con el algoritmo de hashing bcrypt para el que Spring Security provee una implementación.

Autenticación

Procedamos a realizar la configuración de autenticación (el sistema de «logado» de los usuarios). Spring Security apuesta por la flexibilidad, lo que se traduce en una enorme modularidad basada en el empleo intensivo de interfaces y clases abstractas.

Necesitamos una implementación de la interfaz UserDetailsService. Su único método provee, partiendo del nombre del usuario, una instancia de UserDetails con los datos requeridos por Spring Security para efectuar los procesos de autenticación y autorización. La buena noticia es que para realizar la autenticación contra una base de datos ya contamos con JdbcUserDetailsManager, un UserDetailsService listo para ser usado sin apenas configuración.

Lo que haremos es crear una clase de tipo @Configuration llamada, por ejemplo, SpringSecurityConfig. Aunque la clase Main de Spring Boot ya es de ese tipo, recomiendo organizar en otras clases las configuraciones. El motivo es que esta estrategia nos dará flexibilidad de cara al desarrollo de los tests y al diseño de configuraciones complejas que requieran, por ejemplo, el empleo de perfiles para activar o desactivar funcionalidades.

En un método anotado con @Bean, devolvemos nuestro UserDetailsService. En otro, establecemos bcrypt como el codificador de contraseñas (PasswordEncoder) predeterminado de Spring que será aplicado de forma automática sin nuestra intervención cuando sea necesario.

@Configuration
class SpringSecurityConfig {

    @Bean
    UserDetailsService jdbcUserDetailsManager(DataSource dataSource,
                                              @Value("${security-jdbc.user}") String usersByUsernameQuery,
                                              @Value("${security.jdbc-authorities}") String authoritiesByUsernameQuery) {
        JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
        jdbcUserDetailsManager.setDataSource(dataSource);
        jdbcUserDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
        jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(authoritiesByUsernameQuery);
        return jdbcUserDetailsManager;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

Se ha necesitado la fuente de datos para crear un JdbcUserDetailsManager (la interfaz DataSource proporciona las conexiones a la base de datos), así como dos consultas SQL que he puesto en el fichero application.properties. Ambas reciben el nombre del usuario.

  • Consulta de autenticación. Devuelve el nombre, la contraseña y un número que indique si el usuario está activo (1) o deshabilitado.
    security-jdbc.user=select name, password, enabled from users where name=?
  • Consulta de obtención de roles. Devuelve el nombre de usuario y sus roles asociados.
    security.jdbc-authorities =select name, rol from users where name=?  

Si no especificamos alguna consulta, se usará la predeterminada.

select username,password,enabled from users where username = ?
select username,authority from authorities where username = ?

¡Eso fue todo! Ya disponemos en la API REST un sistema de autenticación.

La aplicación puede ejecutarse con el comando mvnw spring-boot:run utilizando el wrapper de Maven incluido en la raíz del proyecto, o bien con un IDE lanzando la clase SpringBootApp. Probémosla con curl llamando al servicio GET de countries.

Si no proporcionamos las credenciales, recibiremos un código HTTP 401.

curl http://localhost:8080/api/countries/1/  -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/countries/1/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 401 
< Set-Cookie: JSESSIONID=7B9D37CC28A064B7FCCFC8B862B26E7A; Path=/api; HttpOnly
< WWW-Authenticate: Basic realm="Realm"
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Length: 0

Ídem si las credenciales son incorrectas.

curl http://localhost:8080/api/countries/1/ -u "user:password" --basic -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'user'
> GET /api/countries/1/ HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 401 
* Authentication problem. Ignoring this.
< WWW-Authenticate: Basic realm="Realm"
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Set-Cookie: JSESSIONID=BEF5DCFA84986FFABA6BB980F7E2E72F; Path=/api; HttpOnly
* Authentication problem. Ignoring this.
< WWW-Authenticate: Basic realm="Realm"
< Content-Length: 0


Finalmente, con las credenciales correctas todo funciona a la perfección y veremos la cadena con los datos en formato JSON.

curl http://localhost:8080/api/countries/1/ -u "user:user" --basic -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'user'
> GET /api/countries/1/ HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjp1c2Vy
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 
< Set-Cookie: JSESSIONID=5341F493485BEFA9943D1A012704EA59; Path=/api; HttpOnly
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
{"id":1,"name":"Spain","population":49067981}

Testing con MockMVC

Mejor todavía si las pruebas anteriores se implementan en tests automáticos, fundamentales para que nuestras aplicaciones sean mantenibles a lo largo del tiempo. Podremos ejecutarlos cada vez que cambiemos el código para comprobar que todo continúa funcionando de la forma esperada. Todo lo necesario lo explico en este tutorial, salvo el uso de Spring Security en las pruebas. Por fortuna no necesitamos mucho, tan solo el siguiente starter.

 <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
 </dependency>

Gracias a este módulo, en las pruebas para Spring Boot (@SpringBootTest) con MockMVC (@AutoConfigureMockMvc) recurrimos a los métodos de la clase SecurityMockMvcRequestPostProcessor para añadir a las llamadas los datos relativos a la autenticación. En nuestro caso, será httpBasic.

package com.danielme.springsecuritybasic.controllers;

import com.danielme.springsecuritybasic.services.CountryService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.is;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class CountryRestControllerTest {

    private static final long MEXICO_ID = 2;
    private static final String USER_STANDARD = "user";
    private static final String USER_ADMIN = "admin";
    public static final String URL = CountryRestController.COUNTRIES_RESOURCE +   CountryRestController.COUNTRIES_ID_PATH;

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    CountryService countryService;

    @Test
    void testGetCountryNoAuthenticacion() throws Exception {
        mockMvc.perform(get(URL, MEXICO_ID))
                .andExpect(status().is(HttpStatus.UNAUTHORIZED.value()));
    }

    @Test
    void testGetCountryWrongUser() throws Exception {
        mockMvc.perform(get(URL, MEXICO_ID)
                .with(httpBasic(USER_STANDARD, "password")))
                .andExpect(status().is(HttpStatus.UNAUTHORIZED.value()));
    }

    @Test
    void testGetCountrySuccess() throws Exception {
        mockMvc.perform(get(URL, MEXICO_ID)
                .with(httpBasic(USER_STANDARD, USER_STANDARD)))
                .andExpect(status().is(HttpStatus.OK.value()))
                .andExpect(jsonPath("$.name", is(countryService.findById(MEXICO_ID).get().getName())));
    }

}

Autorización

En el estado actual del proyecto, todas las llamadas requieren autenticación. A partir de ahí, vía libre para interactuar con cualquier recurso sin limitación alguna. Ahora vamos a configurar la autorización, esto es, qué pueden hacer los usuarios ya autenticados en función de sus roles. Queremos imponer las siguientes condiciones.

  • El acceso a /api/test será público. Ni siquiera se requerirá autenticación, tal y como sucede en estos momentos.
  • Todos los usuarios puede solicitar el GET de /api/countries/{id}/ sin importar su rol.
  • El borrado de un país está limitado a los usuarios con el rol ADMIN.

Lo primero que haremos es anotar SpringSecurityConfig con @EnableWebSecurity. Incluye @Configuration, por lo que ya no la necesitamos.

@EnableWebSecurity
class SpringSecurityConfig {

Creamos un bean de tipo SecurityFilterChain con los requisitos de seguridad que deben verificar todas las peticiones. Lo construimos de manera fluida encadenando llamadas a los numerosos métodos de HttpSecurity, cuya instancia Spring puede inyectar en el método factoría.

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().csrf().disable()
           .authorizeRequests()
           .antMatchers(HttpMethod.GET, CountryRestController.COUNTRIES_RESOURCE+   "/*").authenticated()
           .antMatchers(HttpMethod.DELETE, CountryRestController.COUNTRIES_RESOURCE + "/*").hasRole(UserRol.ADMIN.name())
           .antMatchers("/**").permitAll()
          .and().httpBasic();
  return http.build();
}

En filterChain deshabilitamos la creación de sesiones porque las APIs REST son stateless. Tampoco se requiere seguridad frente a ataques csrf. También forzamos el método de autenticación a BASIC.

Pero lo más interesante no son las configuraciones anteriores, sino las de acceso a nivel de url definidas con los métodos antMatcher. Debemos ser muy cuidadosos con el orden porque al solicitarse una url, esta se va comprobando secuencialmente con los antMatchers siguiendo el orden en el que se han definido hasta encontrar el primero que admita la url solicitada. Por ello, iremos definiendo los patrones empezando por los más específicos, hasta llegar a los más generales. Si una URL no verifica ningún patrón, no se aplica a la petición configuración de seguridad alguna. Esto implica que si se proporcionan credenciales, serán ignoradas.

Para cada patrón de url indicamos la autorización, siendo las opciones habituales las siguientes:

  • authenticated(): cualquier usuario autenticado.
  • hasRole(), hasAnyRole(String):sólo los usuarios autenticados con cierto rol (hasRole) o alguno de los indicados (hasAnyRole).
  • permitAll(): acceso libre.
  • denyAll(): acceso siempre prohibido.

Las restricciones impuestas son coherentes con las pruebas que ya tenemos en CountryRestControllerTest. Escribamos un par de nuevas pruebas que comprueben que la operación DELETE solo puede realizarla un usuario con el rol de administrador.

@Test
void testDeleteCountryWrongAuthorization() throws Exception {
    mockMvc.perform(delete(URL, MEXICO_ID)
            .with(httpBasic(USER_STANDARD, USER_STANDARD)))
            .andExpect(status().is(HttpStatus.FORBIDDEN.value()));
}

@Test
void testDeleteCountrySuccessful() throws Exception {
    mockMvc.perform(delete(URL, -1)
            .with(httpBasic(USER_ADMIN, USER_ADMIN)))
            .andExpect(status().is(HttpStatus.NO_CONTENT.value()));
}

En testDeleteCountryWrongAuthorization se testea que la llamada al método DELETE con el usuario user se rechaza con un código un 403, que no es lo mismo que 401. El 403 informa que la autenticación es correcta, pero falla la autorización y el acceso al recurso solicitado se ha prohibido.

Queda por probar el otro servicio REST. Las llamadas a http://localhost:8080/api/test se podrán realizar sin utilizar usuario alguno (testAnonymous). Eso sí, cuando se proporcionen las (innecesarias) credenciales, deberán ser correctas (testAnonymousButWrongCredentials).

@SpringBootTest
@AutoConfigureMockMvc
class TestRestControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testAnonymous() throws Exception {
        mockMvc.perform(get(TestRestController.TEST_RESOURCE))
                .andExpect(status().is(HttpStatus.NO_CONTENT.value()));
    }

    @Test
    void testAnonymousButWrongCredentials() throws Exception {
        mockMvc.perform(get(TestRestController.TEST_RESOURCE).with(httpBasic("anything", "anything")))
                .andExpect(status().is(HttpStatus.UNAUTHORIZED.value()));
    }

}

Una ventaja de las pruebas automáticas que exploto con frecuencia en el blog es la posibilidad de hacer experimentos y comprobar fácilmente las consecuencias. Veamos qué sucede si cambiamos el orden de la declaración de los antMatcher y situamos en primer lugar el que contiene un patrón válido para cualquier dirección y que permite el acceso anónimo.

 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
           .and().csrf().disable()
           .authorizeRequests()
           .antMatchers("/**").permitAll()
           .antMatchers(HttpMethod.GET, CountryRestController.COUNTRIES_RESOURCE+ "/*").authenticated()
           .antMatchers(HttpMethod.DELETE, CountryRestController.COUNTRIES_RESOURCE + "/*").hasRole(UserRol.ADMIN.name())
           .and().httpBasic();

A todas las peticiones se aplica el primer antMatcher, así que los demás son inútiles. Por ello, fallan testGetCountryNoAuthenticacion (espera se devuelva un 401 porque no se enviaron las credenciales) y testDeleteCountryWrongAuthorization (comprueba que el borrado no está permitido para el rol user).

El experimento anterior pone de manifiesto la necesidad de ser rigurosos en la declaración de los antMatchers, así como la utilidad del testing automático.

Autorización de métodos

Las restricciones de autorización que hemos declarado con HttpSecurity también pueden definirse con la anotación @PreAuthorize, aplicable a métodos, clases e interfaces. Dentro de ella escribiremos las restricciones en una cadena con el lenguaje Spring EL.

    @PreAuthorize("isAuthenticated()")
    @GetMapping
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void testEndpoint() {
        // nothing
    }

Para que la anotación se aplique es necesario activarla.

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SpringSecurityConfig {

Con estos cambios, la prueba testAnonymous fallará porque el solicitante de la petición no esté autenticado (no verifica isAuthenticated).

Acceder al usuario

Los datos y roles del usuario autenticado en Spring Security se guardan en una instancia de la interfaz Authentication que, a su vez, hereda de la interfaz Principal de Java estándar.

Accedemos a ella de forma estática desde cualquier lugar de la aplicación a través de la clase SecurityContextHolder.

logger.info(SecurityContextHolder.getContext().getAuthentication().getName());

En los controladores podemos recibirla como un argumento de los métodos.

@DeleteMapping(value = COUNTRIES_ID_PATH)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteById(@PathVariable("id") Long id, Authentication authentication) {
        logger.info(authentication.getName());

Configuración en XML

La configuración de Spring Security puede hacerse en ficheros XML estilo old-school. Es una práctica en claro desuso, aunque estuvo en boga durante varios años después de que se introdujera la configuración programática en Spring. Sea como fuere, dejo a continuación el fichero (/src/main/resources/security.xml) con la configuración equivalente a la que hemos realizado en la clase SpringSecurityConfig. Quizás consiga evocar la nostalgia de los lectores más veteranos.

<beans:beans
    xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security.xsd">

    <http use-expressions="true" create-session="stateless">
        <csrf disabled="true" />
        <http-basic />
        <intercept-url pattern="/countries/**" method="GET"
            access="isAuthenticated()" />
        <intercept-url pattern="/countries/**"
            method="DELETE" access="hasRole('ADMIN')" />
        <intercept-url pattern="/**" access="permitAll()" />
    </http>

    <authentication-manager
        alias="authenticationManager">
        <authentication-provider>
            <password-encoder hash="bcrypt" />
            <jdbc-user-service
                data-source-ref="dataSource"
                users-by-username-query="select name, password, enabled from users where name=?"
                authorities-by-username-query="select name, rol from users where name=?" />
        </authentication-provider>
    </authentication-manager>

</beans:beans>

Incluimos la configuración del fichero en Spring.

@ImportResource("classpath:security.xml")
public class SpringBootApp {

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Otros tutoriales relacionados con Spring

Introducción a Spring Boot: Aplicación Web con servicios REST y Spring Data JPA

Spring Boot: Gestión de errores en aplicaciones web y REST

Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas

Spring JDBC Template: simplificando el uso de SQL

Persistencia en BD con Spring Data JPA

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.