Stack Tecnológica
Backend com Spring Boot
O framework Spring Boot otimiza o desenvolvimento de aplicações Java ao eliminar a necessidade de configurações XML extensas. Com servidores web embutidos como Tomcat, Jetty e Undertow, ele permite a execução imediata sem dependências externas. Seu mecanismo de autoconfiguração analisa o classpath e ajusta automaticamente os beans do Spring, acelerando a integração com módulos como Spring Data, Spring Security e Spring Cloud. Essa abordagem "convention over configuration" é fundamental para a construção de microsserviços e APIs RESTful escaláveis.
Frontend com Vue.js
No lado do cliente, o Vue.js utiliza uma arquitetura reativa e um DOM Virtual para otimizar a renderização da interface. Seu sistema de componentes e o enlace de dados bidirecional (two-way data binding) permitem que os desenvolvedores foquem na lógica de negócios e na manipulação de estado, enquanto o framework gerencia eficientemente as atualizações cirúrgicas no DOM real, garantindo alta performance e manutenibilidade em aplicações SPA (Single Page Applications).
Camada de Persistência com MyBatis-Plus
Para o aceso a dados, o MyBatis-Plus atua como uma extensão robusta do MyBatis tradicional. Ele fornece operações CRUD genéricas, paginação nativa, controle de concorrência (optimistic locking) e geradores de código. Isso reduz drasticamente a escrita de SQL repetitivo e otimiza o mapeamento objeto-relacional (ORM) para bancos de dados relacionais como MySQL, PostgreSQL e Oracle, elevando a produtividade na camada de repositório.
Engenharia de Testes e Validação
A garantia de qualidade do sistema é conduzida através de testes de caixa preta, focando em fluxos de usuário, valores limite, validação de campos obrigatórios e tratamento de exceções. O objetivo é assegurar que os módulos atendam rigorosamente aos requisitos funcionais e não funcionais antes da impalntação em produção.
Testes do Módulo de Autenticação
A validação do fluxo de login verifica a integridade das credenciais, a validação de captcha e o controle de permissões baseadas em roles.
| Cenário de Teste | Dados de Entrada | Resultado Esperado | Status |
|---|---|---|---|
| Login com credenciais válidas | user: admin_travel | pass: SecurePass123 | captcha: correto | Geração de Token e redirecionamento para o dashboard | Aprovado |
| Login com senha incorreta | user: admin_travel | pass: WrongPass | captcha: correto | Bloqueio e mensagem de erro "Credenciais inválidas" | Aprovado |
| Login com captcha inválido | user: admin_travel | pass: SecurePass123 | captcha: incorreto | Bloqueio e mensagem "Falha na verificação de segurança" | Aprovado |
| Login com campo obrigatório vazio | user: vazio | pass: SecurePass123 | captcha: correto | Alerta de validação de formulário no frontend | Aprovado |
Testes do Módulo de Gestão de Usuários
Este módulo valida as operações de criação, leitura, atualização e exclusão (CRUD), garantindo a integridade referencial e a validação de regras de negócio.
| Cenário de Teste | Ação Executada | Resultado Esperado | Status |
|---|---|---|---|
| Cadastro de novo usuário | Inserir dados válidos e únicos | Registro salvo com sucesso e exibido na listagem | Aprovado |
| Cadastro com identificador duplicado | Inserir e-mail ou username já existente | Falha no cadastro com alerta de violação de unicidade | Aprovado |
| Atualização de perfil | Modificar dados pessoais e salvar | Persistência das alterações e atualização da UI | Aprovado |
| Exclusão de conta | Selecionar usuário e confirmar exclusão | Remoção lógica/física e atualização da tabela de dados | Aprovado |
Implementação de Código
Controlador de Autenticação e Gerenciador de Tokens
@RestController
@RequestMapping("/api/v1/auth")
public class AuthenticationController {
@Autowired
private UserAccountService accountService;
@Autowired
private TokenManager tokenManager;
@Autowired
private PasswordEncoder passwordEncoder;
@IgnoreAuthentication
@PostMapping("/signin")
public ApiResult<AuthResponse> authenticate(@RequestBody LoginRequest request, HttpServletRequest httpRequest) {
UserAccount account = accountService.findByLoginId(request.getLoginId());
if (account == null || !passwordEncoder.matches(request.getSecretKey(), account.getHashedPassword())) {
return ApiResult.failure(401, "Credenciais inválidas ou usuário não encontrado.");
}
String accessToken = tokenManager.issueToken(account.getId(), request.getLoginId(), account.getRole());
return ApiResult.success(new AuthResponse(accessToken));
}
}
@Service
public class TokenManager {
@Autowired
private TokenRepository tokenRepository;
public String issueToken(Long userId, String loginId, String role) {
Optional<AuthToken> existingToken = tokenRepository.findByUserIdAndRole(userId, role);
String newToken = UUID.randomUUID().toString().replace("-", "");
LocalDateTime expiration = LocalDateTime.now().plusHours(2);
if (existingToken.isPresent()) {
AuthToken token = existingToken.get();
token.setAccessToken(newToken);
token.setExpiresAt(expiration);
tokenRepository.save(token);
} else {
AuthToken token = new AuthToken(userId, loginId, role, newToken, expiration);
tokenRepository.save(token);
}
return newToken;
}
}
Interceptador de Segurança e CORS
@Component
public class SecurityInterceptor implements HandlerInterceptor {
private static final String AUTH_HEADER = "Authorization";
@Autowired
private TokenRepository tokenRepository;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
configureCors(response, request);
if (RequestMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return false;
}
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
if (method.hasMethodAnnotation(IgnoreAuthentication.class)) {
return true;
}
}
String headerToken = request.getHeader(AUTH_HEADER);
if (StringUtils.hasText(headerToken)) {
Optional<AuthToken> validToken = tokenRepository.findByAccessToken(headerToken);
if (validToken.isPresent() && validToken.get().getExpiresAt().isAfter(LocalDateTime.now())) {
request.setAttribute("currentUserId", validToken.get().getUserId());
request.setAttribute("currentRole", validToken.get().getRole());
return true;
}
}
writeUnauthorizedResponse(response);
return false;
}
private void configureCors(HttpServletResponse response, HttpServletRequest request) {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With");
response.setHeader("Access-Control-Allow-Credentials", "true");
}
private void writeUnauthorizedResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
try (PrintWriter writer = response.getWriter()) {
writer.write("{\"code\":401,\"message\":\"Sessão expirada ou não autenticada.\"}");
} catch (IOException e) {
throw new RuntimeException("Falha ao escrever resposta de erro", e);
}
}
}
Estrutura do Banco de Dados
Modelagem da tabela responsável pelo armazenamento e controle de expiração das sessões de acesso (tokans).
-- --------------------------------------------------------
-- Estrutura da tabela para gerenciamento de tokens de acesso
-- --------------------------------------------------------
DROP TABLE IF EXISTS `auth_tokens`;
CREATE TABLE `auth_tokens` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Identificador único',
`user_id` bigint(20) NOT NULL COMMENT 'Referência ao usuário',
`login_name` varchar(100) NOT NULL COMMENT 'Identificador de login',
`role` varchar(50) DEFAULT NULL COMMENT 'Perfil de acesso',
`access_token` varchar(255) NOT NULL COMMENT 'Hash do token de sessão',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data de emissão',
`expires_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Data de expiração',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_access_token` (`access_token`),
KEY `idx_user_role` (`user_id`, `role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tabela de controle de sessões';
-- --------------------------------------------------------
-- Dados iniciais para testes de integração
-- --------------------------------------------------------
INSERT INTO `auth_tokens` (`user_id`, `login_name`, `role`, `access_token`, `created_at`, `expires_at`)
VALUES
(101, 'admin_master', 'ADMIN', 'a8f5f167f44f4964e6c998dee827110c3b2a3f9f', '2023-10-01 08:00:00', '2023-10-01 10:00:00'),
(102, 'user_traveler', 'CUSTOMER', 'b9g6g278g55g5075f7d009eff938221d4c3b4g0g', '2023-10-01 09:15:00', '2023-10-01 11:15:00'),
(103, 'guide_silva', 'GUIDE', 'c0h7h389h66h6186g8e110fgg049332e5d4c5h1h', '2023-10-01 10:30:00', '2023-10-01 12:30:00');