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.
Explicação:
- Aplicativo React: O frontend que faz requisições HTTP para a API RESTful.
- 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.
- Camada de Segurança (Spring Security): Gerencia a autenticação e autorização dos usuários.
- /login: Endpoint onde os usuários enviam suas credenciais para obter um token JWT.
- Verifica Autoridade: Confirma se o usuário possui a autoridade necessária para acessar recursos específicos.
- Banco de Dados Postgres: Armazena os dados da aplicação.
- JPA: Utilizado para interagir com o banco de dados através de entidades.
- 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.
- 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. - 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
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.
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
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.
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:
- 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
}
}
- 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
SecurityConfigdefine um bean do tipoPasswordEncoder, configurado para usar BCrypt. - Registro de Usuário: No método
registerUserda classeUserService, a senha do usuário é criptografada usandopasswordEncoder.encode(password), e a senha criptografada pode ser armazenada no banco de dados. - Validação de Senha: O método
validateUserusapasswordEncoder.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
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
Palavras-Chave, Recursos e Exemplos de Prompt
-
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."
-
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."
-
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."
-
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
Usuariocom os campos nome, email, e senha."
-
PostgreSQL
- Link de Referência: Documentação do PostgreSQL
- Prompt de IA: "Gere um script SQL para criar uma tabela de usuários no PostgreSQL."
-
Spring Security
- Link de Referência: Documentação do Spring Security
- Prompt de IA: "Crie um exemplo de configuração do Spring Security para autenticação usando JWT."
-
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."
-
Docker
- Link de Referência: Documentação do Docker
- Prompt de IA: "Gere um Dockerfile para uma aplicação Spring Boot."
-
Docker Compose
- Link de Referência: Documentação do Docker Compose
- Prompt de IA: "Crie um arquivo
docker-compose.ymlpara orquestrar uma aplicação Spring Boot com um banco de dados PostgreSQL."
-
CommandLineRunner
- Link de Referência: Spring Boot CommandLineRunner
- Prompt de IA: "Gere um exemplo de uso do
CommandLineRunnerpara inicializar dados em uma aplicação Spring Boot."
-
BCrypt
- Link de Referência: Documentação do BCrypt no Spring Security
- Prompt de IA: "Crie um exemplo de como usar BCrypt para criptografar senhas em uma aplicação Spring Boot."
-
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."
-
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."
-
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."
-
Entidades JPA
- Link de Referência: JPA Entities
- Prompt de IA: "Crie um exemplo de entidade JPA para um objeto
Usuariocom as anotações adequadas."
-
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."
