📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Backend com Java e Spring Boot. Recomendado revisar antes de começar.
🔑 Login de Usuários 02 — Refatoração SOLID/DRY
Trilha de Aprendizado — Projeto 2 de 4
- 01: Login/cadastro com role única (enum) · Bootstrap 5 · H2
- ➡️ 02 (este): Refatoração SOLID/DRY — camada de Service, DTOs, Bean Validation
- 03: RBAC muitos-para-muitos · painel admin · relatórios
- 04: Levantamento de Requisitos · Backlog & Sprints · Git Flow · testes · ADRs
🎓 Nível profissional simulado: Júnior. As features não mudam nada em relação ao
_01— o que muda é como o código é organizado. Um júnior já reconhece que “funciona” não é o mesmo que “está bem estruturado”, e sabe nomear o problema: violação de responsabilidade única, entidade JPA exposta direto no formulário web, ausência de validação de entrada.`
Este projeto parte do código do Login de Usuários 01 sem adicionar nenhuma feature nova — o objetivo é só refatorar. Comparar os dois projetos lado a lado é o próprio material de estudo.`
_01CustomUserDetailsService fazendo duas coisasNo _01, a classe que implementa UserDetailsService (contrato do Spring Security para autenticação) também tinha um método save(User user) usado pelo cadastro. Duas responsabilidades sem relação direta — autenticar vs. cadastrar — na mesma classe:
// _01 (antes)
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) { /* ... */ }
public void save(User user) { /* lógica de cadastro aqui, misturada */ }
}
Fix: CustomUserDetailsService volta a fazer só uma coisa (loadUserByUsername). Toda a regra de negócio de usuário (cadastrar, listar, apagar, criar admin) migra para uma classe nova, UserService, usada tanto pelo AuthController (auto-cadastro) quanto pelo UserController (painel admin) — nenhum dos dois fala com o UserRepository diretamente.
No _01, o formulário de cadastro fazia bind direto na entidade User:
// _01 (antes)
@PostMapping("/registar")
public String processRegistration(@ModelAttribute User user) {
user.setRole(Role.USER);
userService.save(user);
return "redirect:/login?success";
}
Isso significa que o formulário HTML tem acesso indireto a todos os campos da entidade, incluindo role e id — só não vira uma falha de segurança real aqui porque o role é sobrescrito manualmente logo em seguida. Mas o desenho do código deixa essa porta aberta por padrão.
Fix: um DTO dedicado (RegisterUserDTO) só com os 3 campos que o formulário de fato deveria expor:
public class RegisterUserDTO {
@NotBlank(message = "O nome é obrigatório")
private String nome;
@NotBlank(message = "O e-mail é obrigatório")
@Email(message = "Informe um e-mail válido")
private String email;
@NotBlank(message = "A senha é obrigatória")
@Size(min = 6, message = "A senha deve ter pelo menos 6 caracteres")
private String senha;
// getters/setters
}
role não existe no DTO — não porque alguém lembrou de filtrar, mas porque é estruturalmente impossível de enviar. Essa é a diferença entre “seguro por enquanto” e “seguro por construção”.
No _01, UserController injetava UserRepository e chamava findAll()/deleteById() direto — pulando a camada de serviço inteira:
// _01 (antes)
@Autowired
private UserRepository userRepository;
@GetMapping
public String listUsers(Model model) {
model.addAttribute("users", userRepository.findAll());
return "usuarios";
}
Fix: o controller passa a depender só de UserService. Hoje listAll()/deleteById() só repassam pro repositório, mas a regra de negócio tem onde crescer (ex.: “não permitir que um ADMIN se auto-apague”) sem tocar no controller.`
UserRepositorysó é injetado dentro de classes deservice/— nenhum@ControllerimportaUserRepository(confira comgrep -r UserRepository src/main/java/.../controller/, deve vir vazio).- Tente cadastrar um usuário com e-mail inválido ou senha de 3 caracteres — a tela de cadastro deve mostrar a mensagem de erro em vez de deixar o H2 rejeitar silenciosamente.
- O comportamento visível da aplicação (quem consegue logar, quem é ADMIN, quais rotas são protegidas) é idêntico ao
_01— se algo mudou de comportamento, a refatoração foi longe demais.`
cd loginusuarios
./mvnw spring-boot:run
Acesse http://localhost:8080/login — admin padrão: admin@email.com / admin123.
—`
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd javaweb_login_02_bcrypt
./mvnw spring-boot:run
./mvnw test
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta navegar até a pasta antes de executar: cd proj_aplicacoes_full_stack/projetos/javaweb_login_02_bcrypt