MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Autenticação com Spring Security e MongoDB

É simplesmente difícil obter visibilidade verdadeira, em tempo real em uma autenticação em execução fluxo.

Partes do processo podem ser completamente escondidas de nós; se o processo de autorização completo requer um redirecionamento de um servidor de produção OAuth remoto, todo esforço de depuração deve passar pelo servidor de produção.

É praticamente inviável depurar isso localmente. Não há como reproduzir o estado exato e não há como inspecionar o que realmente está acontecendo nos bastidores. Não é o ideal.

Conhecendo esses tipos de desafios, criamos o Lightrun - uma ferramenta de depuração de produção em tempo real - para permitir que você entenda fluxos complicados com informações em nível de código. Adicione logs, tire instantâneos (pontos de interrupção virtuais) e instrumentos de métricas sem um depurador remoto, sem interromper o serviço em execução e, o mais importante - em tempo real e sem efeitos colaterais .

Saiba mais com este tutorial de 5 minutos focado em depurar esses tipos de cenários usando Lightrun:

>> Depuração de autenticação e autorização usando Lightrun

1. Visão geral


Spring Security oferece diferentes sistemas de autenticação, como por meio de um banco de dados e UserDetailService .

Em vez de usar uma camada de persistência JPA, também podemos usar, por exemplo, um repositório MongoDB. Neste tutorial, veremos como autenticar um usuário usando Spring Security e MongoDB.

2. Autenticação de segurança Spring com MongoDB


Semelhante a usar um repositório JPA, podemos usar um repositório MongoDB . No entanto, precisamos definir uma configuração diferente para usá-lo.

2.1. Dependências do Maven


Para este tutorial, usaremos o MongoDB incorporado . No entanto, uma instância do MongoDB e Testcontainer podem ser opções válidas para um ambiente de produção. Primeiro, vamos adicionar o spring-boot-starter-data-mongodb e de.flapdoodle.embed.mongo dependências:
<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. Configuração


Depois de definir as dependências, podemos criar nossa configuração:
@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");
    }
}

Também precisamos configurar nosso AuthenticationManager com, por exemplo, uma autenticação 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. Domínio e repositório do usuário


Primeiro, vamos definir um usuário simples com funções para nossa autenticação. Faremos com que ele implemente os UserDetails interface para reutilizar métodos comuns de um 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
}

Agora que temos nosso usuário, vamos definir um repositório simples:
public interface UserRepository extends MongoRepository<User, String> {

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

2.4. Serviço de autenticação


Finalmente, vamos implementar nosso UserDetailService para recuperar um usuário e verificar se ele 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. Autenticação de teste


Para testar nossa aplicação, vamos definir um controlador simples. Como exemplo, definimos duas funções diferentes para testar a autenticação e a autorização para endpoints 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!";
    }

}

Vamos encerrar tudo em um Spring Boot Test para verificar se nossa autenticação funciona. Como podemos ver, esperamos um código 401 para alguém que fornece credenciais inválidas ou que não existe em nosso 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());
    }
}