Este post nos llevará a través del proceso de crear una aplicación de registro y login usando Spring Webflux Security y MongoDB. Por favor lee mi previo post Spring Webflix Security antes de continuar con esta información. Vamos a empezar agregando las dependencias de MongoDB y Lombok al archivo build.gradle
implementation('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')
Lombok es una gran herramienta para ahorrarnos código, para saber más acerca de Lombok por favor ve aquí. Ahora, necesitamos cambiar la configuración de Spring Security específicamente el método userDetailsService
, así podemos obtener un usuario de la base de datos MongoDB.
package com.jos.dem.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import com.jos.dem.security.repository.UserRepository;
@EnableWebFluxSecurity
public class SecurityConfig {
@Autowired
private UserRepository userRepository;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.httpBasic()
.and()
.formLogin();
return http.build();
}
@Bean
public ReactiveUserDetailsService userDetailsService() {
return (username) -> userRepository.findByUsername(username);
}
}
Desde que estamos en el campo reactivo, userDetailsService()
debería ser reactivo. Así que usemos ReactiveMongoRepository
que regresa un publisher de tipo Mono.
package com.jos.dem.security.repository;
import com.jos.dem.security.model.User;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.security.core.userdetails.UserDetails;
import reactor.core.publisher.Mono;
public interface UserRepository extends ReactiveMongoRepository<User, String> {
Mono<UserDetails> findByUsername(String username);
}
Aquí está nuestra clase user que implementa la especificación UserDetails
.
package com.jos.dem.security.model;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
@Data
@Document
@ToString
public class User implements UserDetails {
@Id
private String username;
private String password;
private boolean active = true;
private Set<GrantedAuthority> roles = new HashSet<GrantedAuthority>();
@Builder
public User(String username, String password){
this.username = username;
this.password = password;
roles.add(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public boolean isAccountNonExpired() {
return active;
}
@Override
public boolean isAccountNonLocked() {
return active;
}
@Override
public boolean isCredentialsNonExpired() {
return active;
}
@Override
public boolean isEnabled() {
return active;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
}
Ahora, vamos a usar CommandLineRunner
para registrar un usuario. El CommandLineRunner
es una interfaz call back en Spring Boot, cuando nuestra aplicación arranca llamará al método start y le pasará los argumentos unsando el método interno run()
package com.jos.dem.security;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import com.jos.dem.security.model.User;
import com.jos.dem.security.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SpringBootApplication
public class DemoApplication {
private Logger log = LoggerFactory.getLogger(this.getClass());
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
CommandLineRunner start(UserRepository userRepository, PasswordEncoder passwordEncoder){
return args -> {
User user = new User("josdem", passwordEncoder.encode("12345678"));
userRepository.save(user).subscribe();
userRepository.findAll().log().subscribe(u -> log.info("user: {}", u));
};
}
}
No olvides crear la base de datos Mongo en tu ambiente local y especificar las credenciales en el archivo application.properties
.
spring.data.mongodb.database=webflux_security
spring.data.mongodb.host=localhost
spring.data.mongodb.username=username
spring.data.mongodb.password=password
Ahora ya estamos listos para iniciar nuestra aplicación con el comando gradle bootRun
y así poder listar nuestra colección de usuarios desde MongoDB.
2019-06-08 15:12:44.026 INFO 91949 - [ntLoopGroup-2-3] : user: User(username=josdem, password={bcrypt}$2a$10$Fyo6YP2SRe5MhOeQPD67KOoCIosizAsqcz98FZLvW0O2GFz10ag0a, active=true, roles=[ROLE_USER])
Finalmente abramos nuestra página principal de la aplicación: http://localhost:8080 y veremos un lindo y atractivo formulario con Bootstrap 4 para poder hacer login de nuestro usuario.
Después de el acceso exitoso, podrás ver este saludo.
Usando Maven
Para el soporte Maven agrega las dependencias de MongoDB y Lombok al archivo pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
Para ejecutar el projecto con Gradle:
gradle bootRun
Para ejecutar el projecto con Maven:
mvn spring-boot:run
Para explorar el proyecto, por favor ve aquí, para descargar el proyecto:
git clone https://github.com/josdem/reactive-webflux-security.git
git fetch
git checkout database