MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Autenticazione con Spring Security e MongoDB

È semplicemente difficile ottenere il vero, visibilità in tempo reale su un'autenticazione in esecuzione flusso.

Parti del processo possono essere completamente nascoste a noi; se il processo di autorizzazione completo richiede un reindirizzamento da un server di produzione OAuth remoto, ogni sforzo di debug deve passare attraverso il server di produzione.

È praticamente impossibile eseguire il debug di questo in locale. Non c'è modo di riprodurre lo stato esatto e non c'è modo di ispezionare ciò che sta effettivamente accadendo sotto il cofano. Non è l'ideale.

Conoscendo questo tipo di sfide, abbiamo creato Lightrun, uno strumento di debug della produzione in tempo reale, per consentirti di comprendere flussi complicati con informazioni a livello di codice. Aggiungi log, scatta istantanee (punti di interruzione virtuali) e strumenta metriche senza un debugger remoto, senza interrompere il servizio in esecuzione e, soprattutto - in tempo reale e senza effetti collaterali .

Ulteriori informazioni con questo tutorial di 5 minuti incentrato sul debug di questo tipo di scenari utilizzando Lightrun:

>> Debug di autenticazione e autorizzazione utilizzando Lightrun

1. Panoramica

Spring Security offre diversi sistemi di autenticazione, ad esempio tramite un database e UserDetailService .

Invece di utilizzare un livello di persistenza JPA, potremmo anche voler utilizzare, ad esempio, un repository MongoDB. In questo tutorial vedremo come autenticare un utente utilizzando Spring Security e MongoDB.

2. Autenticazione di sicurezza di primavera con MongoDB

Simile all'utilizzo di un repository JPA, possiamo utilizzare un repository MongoDB . Tuttavia, dobbiamo impostare una configurazione diversa per poterla utilizzare.

2.1. Dipendenze Maven

Per questo tutorial utilizzeremo MongoDB incorporato . Tuttavia, un'istanza MongoDB e Testcontainer potrebbero essere valide opzioni per un ambiente di produzione. Innanzitutto, aggiungiamo spring-boot-starter-data-mongodb e de.flapdoodle.embed.mongo dipendenze:

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

Una volta impostate le dipendenze, possiamo creare la nostra configurazione:

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

Dobbiamo anche configurare il nostro AuthenticationManager con, ad esempio, un'autenticazione di base 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 utente e repository

Innanzitutto, definiamo un utente semplice con ruoli per la nostra autenticazione. Faremo implementare gli UserDetails interfaccia per riutilizzare i metodi comuni di un Principale oggetto:

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

Ora che abbiamo il nostro utente, definiamo un semplice repository:

public interface UserRepository extends MongoRepository<User, String> {

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

2.4. Servizio di autenticazione

Infine, implementiamo il nostro UserDetailService per recuperare un utente e verificare se è autenticato :

@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. Verifica dell'autenticazione

Per testare la nostra applicazione, definiamo un semplice controller. Ad esempio, abbiamo definito due ruoli diversi per testare l'autenticazione e l'autorizzazione per endpoint specifici:

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

}

Concludiamo il tutto in uno Spring Boot Test per verificare se la nostra autenticazione funziona. Come possiamo vedere, ci aspettiamo un codice 401 per qualcuno che fornisce credenziali non valide o che non esiste nel nostro 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());
    }
}