Pular para conteúdo

Refinamento Sprint 1 - Login de usuários

Visão Geral

Um aplicativo React e um backend Java usando RESTful APIs, a conexão do Java com um banco de dados Postgres via JPA, a camada de segurança do Spring que autentica usuários com base em suas autoridades e gera tokens JWT pela rota /login.

mmd

Explicação:

  1. Aplicativo React: O frontend que faz requisições HTTP para a API RESTful.
  2. API RESTful (Spring Boot): O backend que processa as requisições do React e interage com a camada de segurança e o banco de dados.
  3. Camada de Segurança (Spring Security): Gerencia a autenticação e autorização dos usuários.
  4. /login: Endpoint onde os usuários enviam suas credenciais para obter um token JWT.
  5. Verifica Autoridade: Confirma se o usuário possui a autoridade necessária para acessar recursos específicos.
  6. Banco de Dados Postgres: Armazena os dados da aplicação.
  7. JPA: Utilizado para interagir com o banco de dados através de entidades.
  8. Entidades JPA: Representações de tabelas no banco de dados, que permitem que a aplicação interaja com os dados de forma orientada a objetos.
  9. Fluxo de Autenticação: O usuário se autentica através do endpoint /login, e um token JWT é gerado. Esse token é enviado juntamente com requisições subsequentes para acessar recursos protegidos.
  10. Acesso Permitido/Acesso Negado: O sistema determina se a requisição pode prosseguir com base nas autoridades do usuário.

1. Gestão de ambiente

mmd

Docker

Para rodar os sistemas já temos containeres docker configurados na aplicação, para saber mais sobre docker leia o nosso guia de uso do docker

Docker Compose

Além das configurações docker (Dockerfile), temos uma configuração completa do docker compose que permite rodar todos os microservices e seus respectivos bancos de dados com um único comando, temos tanto para um ambiente de desenvolvimento quanto para um ambiente de produção

para saber mais sobre docker compose leia o nosso guia de uso do docker compose

Comandos para rodar o docker compose da aplicação

Para rodar um versão compilada como se fosse um ambiente produtivo, rodar o coamndo normalmente:

docker compose up -d

para ver o progresso dos serviços rodando, usar o comando de visualização de logs do console

docker compose logs -f

Para rodar um ambiente de desenvolvimento utilizando docker, rode o comando abaixo, foi adicionada a esse projeto uma configuração exclusiva para desenvolvimento, cuja compartilha sua pasta local com os containeres e portanto escutam as suas modificações e atualizam o container automáticamente

docker compose -f docker-compose-dev.yml -d 

Executando comandos dentro do container

O comando docker exec desempenha um papel fundamental na interação com contêineres em execução, permitindo que os desenvolvedores e administradores do sistema executem comandos adicionais dentro de um contêiner já ativo. Essa funcionalidade é especialmente útil para tarefas de administração, depuração e manutenção, pois permite acessar o terminal de um contêiner ou executar scripts e processos sem a necessidade de reiniciar o contêiner ou criar uma nova instância. Por exemplo, ao usar docker exec -it <container_id> /bin/bash, o usuário pode abrir uma sessão de shell interativa dentro do contêiner, facilitando a inspeção do sistema de arquivos, a verificação de logs ou a modificação de configurações em tempo real. Essa capacidade de interagir diretamente com o ambiente do contêiner é essencial para a gestão eficiente de aplicações em contêineres, permitindo resolver problemas rapidamente e realizar atualizações ou ajustes conforme necessário.

mmd

2. História de cadastro do usuário ADMIN

Essa história requer a criação da entidade de usuario com o campo "type" que indica qual o tipo de usuário, esse campo vai nos ajudar a identificar se o usuário é do tipo ADMIN cujo é o tipo de usuário que pode cadastrar outros usuários

«Entity»Usuarionome: stringemail: stringsenha: stringcargo: stringtipo: TipoUsuario«Enumerator»TipoUsuarioADMINPDMCOLABORADOR

Nota

Esse é apenas um modelo ilustrativo, na prática mais campos serão necessários

Aplicação com linha de comando

Em uma aplicação spring estamos também usando o modelo padrão de execução do java, ou seja, o método main recebe os arqumentos normalmente e decidir rodar ou não a aplicação spring pode ser opcional:

@SpringBootApplication
public class UsermanagementApplication {

  public static void main(String[] args) {
    if (args.length > 0) {
      switch (args[0]) {
        case "cadstrar:admin":

          break;
      }
    } else {
      SpringApplication.run(UsermanagementApplication.class, args);
    }
  }

}

ComandLineRunner

O CommandLineRunner é uma interface do Spring que permite a execução de código específico após a inicialização da aplicação, sendo chamada automaticamente pelo Spring Boot quando a aplicação é iniciada. Ao implementar a interface CommandLineRunner, os desenvolvedores podem definir a lógica que deve ser executada logo após o contexto da aplicação ser carregado, o que é útil para tarefas como a inicialização de dados, a execução de verificações de configuração ou a configuração de serviços. O método run(String... args) é onde essa lógica é colocada, e os argumentos passados para a aplicação podem ser acessados através desse método, permitindo personalizar o comportamento da aplicação com base nos parâmetros de inicialização.

mmd

Na nossa aplicação podemos usá-lo para fazer o cadastro do usuário admin, muito embora esse não seja o papel exato dessa interface, porém se dermos um System.exit podemos fazer o cadastro do admin e parar a aplicação, nesse caso podemos realizar o cadastro mediante a termos recebidos os parametros necessário na linha de comando:

@Component
public class AdminRegistrationCommandLineRunner implements CommandLineRunner {

    @Autowired
    private UserService userService;

    @Autowired
    private ApplicationContext context;

    @Override
    public void run(String... args) throws Exception {
        // Verifica se foi invocado o commando que queriamos
        if (args.length == 0 || args[0] != 'register:admin') {
            // caso não seja, significa que estamos rodando a aplicação normalmente, apenas saia do método
            return;
        }

        try {
            String nome = args[1];
            String email = args[2];
            String senha = args[3];

            // chamada de método ilustrativa
            userService.cadastrarUsuario(nome, email, senha);

            System.exit(SpringApplication.exit(context, () -> 0));

        } catch (Exception e) {
            System.err.println("❌ Erro ao cadastrar usuário: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Criptografia de senhas

O Spring Security fornece suporte integrado para a criptografia de senhas usando o algoritmo BCrypt, que é uma abordagem segura e amplamente utilizada para proteger senhas de usuários em aplicações. O BCrypt não apenas criptografa a senha, mas também adiciona um sal aleatório, o que torna a aplicação mais resistente a ataques de força bruta e dicionário. Para usar o BCrypt, você pode injetar uma instância de PasswordEncoder em sua classe de serviço ou controlador e utilizar o método encode() para criptografar a senha do usuário ao registrá-lo e o método matches() para verificar a senha ao autenticar o usuário.

Exemplo de Uso do BCrypt no Spring

Aqui está um exemplo de como usar o BCrypt para criptografar senhas de usuários em uma aplicação Spring:

  1. Configurar o PasswordEncoder:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // Define o encoder para BCrypt
    }
}
  1. Usar o PasswordEncoder no Serviço de Usuário:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class PasswordManager {

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void registerUser(String username, String password) {
        // Criptografa a senha usando BCrypt
        String encryptedPassword = passwordEncoder.encode(password);

        return encryptedPassword;
    }

    public boolean validateUser(String rawPassword, String storedEncryptedPassword) {
        // Verifica se a senha fornecida corresponde à senha criptografada armazenada
        return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
    }
}

Explicação do Código:

  • Configuração do PasswordEncoder: A classe SecurityConfig define um bean do tipo PasswordEncoder, configurado para usar BCrypt.
  • Registro de Usuário: No método registerUser da classe UserService, a senha do usuário é criptografada usando passwordEncoder.encode(password), e a senha criptografada pode ser armazenada no banco de dados.
  • Validação de Senha: O método validateUser usa passwordEncoder.matches(rawPassword, storedEncryptedPassword) para verificar se a senha fornecida pelo usuário corresponde à senha criptografada armazenada.

3. Histórias de Login e Filtro (middleware) de proteção de rotas

Protegendo endpoints com a security lib

alt text

Habilitando o modulo

Os projetos inclusos no desafio já estão com a Lib habilitada, basta adicionar ela no pom.xml

  <dependencies>
    <dependency>
      <groupId>com.ciandt.nextgen25</groupId>
      <artifactId>security</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

Habilitar a funcionalidade no código

Basta incluir a anotação @EnableJwtSecurity em uma classe de configuração

@Configuration
@EnableJwtSecurity
public class SecurityConfig
{

}

Geração de token Jwt usando JwtTokenService

O JwtTokenService é utilizado em uma aplicação Spring para gerenciar a autenticação baseada em tokens JWT (JSON Web Tokens), permitindo uma abordagem segura e escalável para controlar o acesso a recursos. Ao autenticar um usuário, o serviço gera um token que contém informações relevantes, como o e-mail do usuário e suas permissões, assinadas com uma chave secreta. Esse token é então retornado ao cliente, que o utiliza em requisições subsequentes, enviando-o no cabeçalho de autorização. O JwtTokenService também valida o token em cada requisição protegida, garantindo que o usuário esteja autenticado e autorizado a acessar os recursos solicitados. Essa abordagem favorece a construção de APIs RESTful, onde a statelessness (ausência de estado) é essencial, pois cada requisição pode ser autenticada de forma independente através do token.

@PostMapping("/auth/login")
public ResponseEntity<String> login(@RequestBody UserLoginRequest userLoginRequest) {
    // Autentica o usuário com as credenciais fornecidas
    authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(userLoginRequest.getUsername(), userLoginRequest.getPassword())
    );

    // Gera um token JWT após a autenticação bem-sucedida
    String token = jwtTokenService.generateToken(userLoginRequest);
    return ResponseEntity.ok(token); // Retorna o token na resposta
}

Guia de referências

Aqui segue as documentações mais importantes dessa documentação a serem lidas para essa sprint

  1. AI Driven Development

Palavras-Chave, Recursos e Exemplos de Prompt

  1. React

    • Link de Referência: Documentação do React
    • Prompt de IA: "Gere um exemplo de componente funcional em React que exibe uma lista de usuários."
  2. Java

    • Link de Referência: Documentação do Java
    • Prompt de IA: "Crie um exemplo simples de uma classe Java com métodos para cadastrar e listar usuários."
  3. Spring Boot

    • Link de Referência: Documentação do Spring Boot
    • Prompt de IA: "Gere um exemplo de aplicação Spring Boot que configura um endpoint REST para login."
  4. JPA (Java Persistence API)

    • Link de Referência: Documentação do JPA
    • Prompt de IA: "Crie um exemplo de uma entidade JPA para a classe Usuario com os campos nome, email, e senha."
  5. PostgreSQL

  6. Spring Security

  7. JWT (JSON Web Token)

    • Link de Referência: Introdução ao JWT
    • Prompt de IA: "Gere um exemplo de como criar e validar um token JWT em uma aplicação Spring Boot."
  8. Docker

  9. Docker Compose

    • Link de Referência: Documentação do Docker Compose
    • Prompt de IA: "Crie um arquivo docker-compose.yml para orquestrar uma aplicação Spring Boot com um banco de dados PostgreSQL."
  10. CommandLineRunner

    • Link de Referência: Spring Boot CommandLineRunner
    • Prompt de IA: "Gere um exemplo de uso do CommandLineRunner para inicializar dados em uma aplicação Spring Boot."
  11. BCrypt

  12. Middleware (Next.js)

    • Link de Referência: Middleware no Next.js
    • Prompt de IA: "Gere um exemplo de middleware no Next.js que protege uma rota com autenticação JWT."
  13. API RESTful

    • Link de Referência: Conceitos de API RESTful
    • Prompt de IA: "Crie um exemplo de uma API RESTful em Spring Boot que implementa operações CRUD para usuários."
  14. Docker Networking

    • Link de Referência: Docker Networking
    • Prompt de IA: "Gere um exemplo de configuração de rede em Docker para comunicação entre contêineres."
  15. Entidades JPA

    • Link de Referência: JPA Entities
    • Prompt de IA: "Crie um exemplo de entidade JPA para um objeto Usuario com as anotações adequadas."
  16. Exposição de Portas no Docker

    • Link de Referência: Exposição de Portas em Docker
    • Prompt de IA: "Crie um exemplo de comando Docker para iniciar um contêiner, expondo a porta 8080 do host para a porta 80 do contêiner."