MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Authentification avec Spring Security et MongoDB

Il est tout simplement difficile d'obtenir une visibilité en temps réel sur une authentification en cours d'exécution flux.

Des parties du processus peuvent nous être complètement cachées ; si le processus d'autorisation complet nécessite une redirection depuis un serveur de production OAuth distant, alors chaque effort de débogage doit passer par le serveur de production.

Il est pratiquement impossible de déboguer cela localement. Il n'y a aucun moyen de reproduire l'état exact et aucun moyen d'inspecter ce qui se passe réellement sous le capot. Pas idéal.

Connaissant ces types de défis, nous avons conçu Lightrun - un outil de débogage de production en temps réel - pour vous permettre de comprendre les flux complexes avec des informations au niveau du code. Ajoutez des journaux, prenez des instantanés (points d'arrêt virtuels) et instrumentez des métriques sans débogueur distant, sans arrêter le service en cours d'exécution et, surtout - en temps réel et sans effets secondaires .

Apprenez-en plus avec ce tutoriel de 5 minutes concentré sur le débogage de ces types de scénarios à l'aide de Lightrun :

>> Débogage de l'authentification et de l'autorisation à l'aide de Lightrun

1. Présentation

Spring Security propose différents systèmes d'authentification, par exemple via une base de données et UserDetailService .

Au lieu d'utiliser une couche de persistance JPA, nous pouvons également utiliser, par exemple, un référentiel MongoDB. Dans ce didacticiel, nous verrons comment authentifier un utilisateur à l'aide de Spring Security et de MongoDB.

2. Authentification Spring Security avec MongoDB

Semblable à l'utilisation d'un référentiel JPA, nous pouvons utiliser un référentiel MongoDB . Cependant, nous devons définir une configuration différente pour pouvoir l'utiliser.

2.1. Dépendances Maven

Pour ce tutoriel, nous allons utiliser Embedded MongoDB . Cependant, une instance MongoDB et Testcontainer pourraient être des options valides pour un environnement de production. Tout d'abord, ajoutons le spring-boot-starter-data-mongodb et de.flapdoodle.embed.mongo dépendances :

<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. Configuration

Une fois les dépendances définies, nous pouvons créer notre configuration :

@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");
    }
}

Nous devons également configurer notre AuthenticationManager avec par exemple une authentification HTTP basique :

@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. Domaine utilisateur et référentiel

Définissons d'abord un utilisateur simple avec des rôles pour notre authentification. Nous lui ferons implémenter les UserDetails interface pour réutiliser les méthodes communes d'un Principal objet :

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

Maintenant que nous avons notre utilisateur, définissons un référentiel simple :

public interface UserRepository extends MongoRepository<User, String> {

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

2.4. Service d'authentification

Enfin, implémentons notre UserDetailService afin de récupérer un utilisateur et vérifier s'il est authentifié :

@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. Tester l'authentification

Pour tester notre application, définissons un contrôleur simple. À titre d'exemple, nous avons défini deux rôles différents pour tester l'authentification et l'autorisation pour des points de terminaison spécifiques :

@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!";
    }

}

Résumons tout cela dans un Spring Boot Test pour vérifier si notre authentification fonctionne. Comme nous pouvons le voir, nous attendons un code 401 pour quelqu'un qui fournit des informations d'identification invalides ou qui n'existe pas dans notre système :

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());
    }
}