sql >> Base de Datos >  >> NoSQL >> MongoDB

Autenticación con Spring Security y MongoDB

Simplemente es difícil obtener visibilidad real, en tiempo real de una autenticación en ejecución flujo.

Partes del proceso pueden estar completamente ocultas para nosotros; si el proceso de autorización completo requiere una redirección desde un servidor de producción de OAuth remoto, entonces cada esfuerzo de depuración debe pasar por el servidor de producción.

Es prácticamente inviable depurar esto localmente. No hay forma de reproducir el estado exacto y no hay forma de inspeccionar lo que realmente está sucediendo debajo del capó. No es ideal.

Conociendo este tipo de desafíos, creamos Lightrun, una herramienta de depuración de producción en tiempo real, para permitirle comprender flujos complicados con información a nivel de código. Agregue registros, tome instantáneas (puntos de interrupción virtuales) e instrumente métricas sin un depurador remoto, sin detener el servicio en ejecución y, lo que es más importante, en tiempo real y sin efectos secundarios .

Aprende más con este tutorial de 5 minutos centrado en la depuración de este tipo de escenarios mediante Lightrun:

>> Depuración de autenticación y autorización mediante Lightrun

1. Resumen

Spring Security ofrece diferentes sistemas de autenticación, como a través de una base de datos y UserDetailService .

En lugar de usar una capa de persistencia JPA, es posible que también queramos usar, por ejemplo, un repositorio MongoDB. En este tutorial, veremos cómo autenticar a un usuario usando Spring Security y MongoDB.

2. Autenticación Spring Security con MongoDB

Al igual que usar un repositorio JPA, podemos usar un repositorio MongoDB . Sin embargo, necesitamos establecer una configuración diferente para poder usarlo.

2.1. Dependencias Maven

Para este tutorial, vamos a utilizar Embedded MongoDB . Sin embargo, una instancia de MongoDB y Testcontainer podrían ser opciones válidas para un entorno de producción. Primero, agreguemos spring-boot-starter-data-mongodb y de.flapdoodle.embed.mongo dependencias:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <version>3.3.1</version>
</dependency>

2.2. Configuración

Una vez que establecemos las dependencias, podemos crear nuestra configuración:

@Configuration
public class MongoConfig {

    private static final String CONNECTION_STRING = "mongodb://%s:%d";
    private static final String HOST = "localhost";

    @Bean
    public MongoTemplate mongoTemplate() throws Exception {

        int randomPort = SocketUtils.findAvailableTcpPort();

        ImmutableMongodConfig mongoDbConfig = MongodConfig.builder()
          .version(Version.Main.PRODUCTION)
          .net(new Net(HOST, randomPort, Network.localhostIsIPv6()))
          .build();

        MongodStarter starter = MongodStarter.getDefaultInstance();
        MongodExecutable mongodExecutable = starter.prepare(mongoDbConfig);
        mongodExecutable.start();
        return new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, HOST, randomPort)), "mongo_auth");
    }
}

También necesitamos configurar nuestro AuthenticationManager con, por ejemplo, una autenticación básica HTTP:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // ...
    public SecurityConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public AuthenticationManager customAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(@Autowired AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
          .passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()
          .disable()
          .authorizeRequests()
          .and()
          .httpBasic()
          .and()
          .authorizeRequests()
          .anyRequest()
          .permitAll()
          .and()
          .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

2.3. Dominio de usuario y repositorio

Primero, definamos un usuario simple con roles para nuestra autenticación. Haremos que implemente los UserDetails interfaz para reutilizar métodos comunes de un Principal objeto:

@Document
public class User implements UserDetails {
    private @MongoId ObjectId id;
    private String username;
    private String password;
    private Set<UserRole> userRoles;
    // getters and setters
}

Ahora que tenemos nuestro usuario, definamos un repositorio simple:

public interface UserRepository extends MongoRepository<User, String> {

    @Query("{username:'?0'}")
    User findUserByUsername(String username);
}

2.4. Servicio de autenticación

Finalmente, implementemos nuestro UserDetailService para recuperar un usuario y comprobar si está autenticado :

@Service
public class MongoAuthUserDetailService implements UserDetailsService {
    // ...
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        com.baeldung.mongoauth.domain.User user = userRepository.findUserByUsername(userName);

        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();

        user.getAuthorities()
          .forEach(role -> {
              grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole()
                 .getName()));
          });

        return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
    }

}

2.5. Autenticación de prueba

Para probar nuestra aplicación, definamos un controlador simple. Como ejemplo, hemos definido dos roles diferentes para probar la autenticación y la autorización para puntos finales específicos:

@RestController
public class ResourceController {

    @RolesAllowed("ROLE_ADMIN")
    @GetMapping("/admin")
    public String admin() {
        return "Hello Admin!";
    }

    @RolesAllowed({ "ROLE_ADMIN", "ROLE_USER" })
    @GetMapping("/user")
    public String user() {
        return "Hello User!";
    }

}

Resumamos todo en una prueba Spring Boot para verificar si nuestra autenticación funciona. Como podemos ver, esperamos un código 401 para alguien que proporciona credenciales no válidas o que no existe en nuestro sistema :

class MongoAuthApplicationTest {

    // set up

    @Test
    void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception {
        mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD)))
          .andExpect(status().isOk());
    }

    @Test
    void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception {
        mvc.perform(get("/user").with(httpBasic("not_existing_user", "password")))
          .andExpect(status().isUnauthorized());
    }

    @Test
    void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception {
        mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password")))
          .andExpect(status().isUnauthorized());
    }

    @Test
    void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception {
        mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD)))
          .andExpect(status().isForbidden());
    }

    @Test
    void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception {
        mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD)))
          .andExpect(status().isOk());

        mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD)))
          .andExpect(status().isOk());
    }
}