Autenticação e autorização
Autenticação responde quem está fazendo a requisição. Autorização decide o que essa identidade pode fazer. O ImobFiscal já exige identidade e bloqueia outro tenant; regras por operação ainda são trabalho futuro.
Portfólio DSM
← Voltar ao PI
Desenvolvimento Web III · Projeto Integrador
Implementação de autenticação stateless, JWT, validação de entrada e isolamento dos dados por imobiliária.
Implementação de Segurança com Spring Security
Esta etapa do Projeto Integrador ImobFiscal acrescentou uma camada de proteção à API REST desenvolvida em Java 17 e Spring Boot 3.3. A mudança foi realizada na disciplina Desenvolvimento Web III e registrada no commit f364c46 — Add Spring Security JWT and validation. O objetivo não foi declarar que o sistema se tornou totalmente seguro, mas corrigir falhas importantes do fluxo anterior e estabelecer uma base verificável para autenticação, autorização, validação de dados e isolamento entre imobiliárias.
O ImobFiscal é um sistema web acadêmico para gerenciamento de imóveis, locadores, contratos, boletos e cálculos fiscais. A solução possui frontend em React, comunicação HTTP por Axios, backend em Spring Boot e persistência relacional com Spring Data JPA. MySQL ou MariaDB são usados nos ambientes de desenvolvimento e produção. O banco H2 aparece somente nos testes automatizados e no perfil local, porque cria um ambiente temporário e reproduzível; ele não substitui o banco de produção.
2–4 · Contexto, problema e objetivos
O fluxo anterior parecia autenticado no navegador, mas o backend não reconhecia de forma confiável a identidade apresentada.
A atividade propôs integrar Spring Security e Jakarta Bean Validation ao Projeto Integrador. O uso de JSON Web Token, ou JWT, era opcional, mas foi adotado por combinar com a API REST stateless do ImobFiscal. A alteração também deveria preservar os testes do motor tributário, evitando que a inclusão da segurança causasse regressão em funcionalidades existentes.
Antes da evolução, o login consultava o usuário e comparava a senha com BCrypt. Quando as credenciais estavam corretas, porém, a API devolvia apenas um UUID aleatório. O frontend armazenava esse valor e o enviava no cabeçalho Authorization, mas o backend não verificava assinatura, validade ou origem. Assim, o token não comprovava identidade e as rotas permaneciam acessíveis sem autenticação efetiva.
| Situação anterior | Risco associado |
|---|---|
| Login retornava UUID aleatório | O valor não podia comprovar a identidade nem ser validado criptograficamente. |
Authorization não era conferido | Uma chamada direta à API podia ignorar o fluxo de login. |
Rotas /api/** abertas | Recursos de locadores, imóveis, contratos e cobranças não exigiam autenticação. |
imobiliariaId recebido pela URL | Um usuário poderia tentar trocar o identificador para consultar outro tenant. |
| Ausência de controle central | Verificações repetidas em controllers poderiam ser esquecidas ou aplicadas de modo desigual. |
5 · Fundamentação teórica
Autenticação, autorização e validação resolvem problemas diferentes. JWT e BCrypt são ferramentas dentro dessa arquitetura, não soluções isoladas.
Autenticação responde quem está fazendo a requisição. Autorização decide o que essa identidade pode fazer. O ImobFiscal já exige identidade e bloqueia outro tenant; regras por operação ainda são trabalho futuro.
Organiza a cadeia de filtros, o contexto autenticado e as decisões de acesso. A configuração libera somente as rotas públicas e exige autenticação nas demais rotas da API.
O token transporta claims e recebe uma assinatura. Ele não criptografa automaticamente o conteúdo: seu papel principal, nesta implementação, é garantir integridade e autenticidade.
Transforma a senha em um hash unidirecional com salt. No login, o backend compara a senha recebida com o hash, sem precisar guardar ou recuperar o texto original.
O servidor não mantém uma sessão tradicional. Cada chamada protegida reapresenta o JWT, que é validado antes de recriar a autenticação daquela requisição.
Anotações nos DTOs registram regras de formato e obrigatoriedade. O @Valid interrompe entradas inválidas antes que elas alcancem a regra de negócio.
Cada imobiliária representa um tenant. O filtro compara banco, token e URL para reduzir o risco de um usuário consultar dados de outra organização.
6–7 · Arquitetura e fluxo
O login produz uma credencial assinada. As requisições seguintes passam por uma nova verificação antes de alcançar as regras do sistema.
Sem token, uma rota pública continua. Uma rota protegida termina em 401.
Assinatura inválida ou token expirado resulta em 401.
O filtro recarrega o cadastro. Usuário inexistente ou tenant incompatível resulta em 401.
Se o UUID da URL for de outra imobiliária, a resposta é 403.
Campos que violam as anotações de validação retornam 400.
Com todas as verificações aprovadas, o controller realiza o processamento normal.
Tecnologias utilizadas
Java 17 e Spring Boot 3.3 formam a base da API. Spring Security organiza os filtros e o contexto autenticado; JJWT 0.12.6 cria e valida o token; BCrypt trata as senhas; e Jakarta Bean Validation verifica os DTOs. A persistência usa Spring Data JPA com MySQL ou MariaDB. No frontend, React e Axios armazenam o estado autenticado e enviam o Bearer token. Os testes utilizam JUnit 5, MockMvc e H2. O H2 existe somente no perfil local e de testes para oferecer um banco temporário; o ambiente de produção continua dependente de um SGBD relacional configurado e de uma JWT_SECRET própria.
8–13 · Funcionamento do código
Os exemplos foram reduzidos para destacar a responsabilidade de cada arquivo. Nenhuma senha, chave ou token real é exibido.
8 · SecurityFilterChain
Arquivo: backend/src/main/java/br/fatec/imobfiscal/config/SecurityConfig.java
O código desativa mecanismos que não pertencem ao fluxo da API, ativa CORS, define a política stateless, libera login, cadastro e health check e protege as demais rotas. Sem essa cadeia, o Bearer token não produziria uma decisão de acesso.
Uma rota protegida sem identidade retorna 401; acesso autenticado, mas proibido, retorna 403. A limitação atual é não aplicar regras detalhadas por perfil e operação.
return http
.csrf(csrf -> csrf.disable())
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.cors(cors -> {})
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/login",
"/api/auth/cadastro", "/api/health").permitAll()
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
.build();
9 · JwtService
Arquivo: backend/src/main/java/br/fatec/imobfiscal/security/JwtService.java
O token inclui o e-mail como subject, além de usuário, imobiliária, perfil, emissão e expiração. A assinatura impede que claims sejam alteradas sem invalidar o token. O payload não é criptografado e não recebe a senha.
A chave é convertida em HMAC e precisa ter ao menos 32 bytes. Em produção, JWT_SECRET deve ser exclusiva, aleatória e diferente da chave usada localmente.
public String gerarToken(Usuario usuario) {
Instant agora = Instant.now();
return Jwts.builder()
.subject(usuario.getEmail())
.claim("usuarioId", usuario.getId().toString())
.claim("imobiliariaId",
usuario.getImobiliariaId().toString())
.claim("perfil", usuario.getPerfil().name())
.issuedAt(Date.from(agora))
.expiration(Date.from(
agora.plusMillis(expirationMs)))
.signWith(chave)
.compact();
}
9 · Validação do JWT
Arquivo: backend/src/main/java/br/fatec/imobfiscal/security/JwtService.java
O parser verifica a assinatura e rejeita tokens expirados. Uma falha produz uma exceção tratada pelo filtro, que limpa o contexto e responde 401.
Essa proteção depende da chave. Uma chave fraca, vazada ou reutilizada entre ambientes compromete a confiança na assinatura, motivo pelo qual o construtor bloqueia valores com menos de 32 bytes.
public Claims validarEObterClaims(String token) {
return Jwts.parser()
.verifyWith(chave)
.build()
.parseSignedClaims(token)
.getPayload();
}
10 · Filtro JWT
Arquivo: backend/src/main/java/br/fatec/imobfiscal/security/JwtAuthenticationFilter.java
O filtro executa uma vez por requisição, lê Authorization e reconhece o prefixo Bearer. Sem token, a chamada continua sem autenticação: uma rota pública funciona, enquanto a configuração bloqueia uma rota privada.
Quando o token existe, o usuário é recarregado pelo e-mail. Isso impede aceitar normalmente um token de usuário que deixou de existir, embora a aplicação ainda não possua uma lista de revogação.
String authorization = request.getHeader(
HttpHeaders.AUTHORIZATION);
if (authorization == null
|| !authorization.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
Claims claims = jwtService.validarEObterClaims(
authorization.substring(7));
Usuario usuario = usuarioService
.buscarPorEmail(claims.getSubject())
.orElseThrow(() ->
new JwtException("Usuário não encontrado"));
10 · SecurityContextHolder
Arquivo: backend/src/main/java/br/fatec/imobfiscal/security/JwtAuthenticationFilter.java
Depois das verificações, o filtro cria um UsernamePasswordAuthenticationToken, adiciona o perfil como autoridade e registra a identidade no contexto. O controller passa a ser executado como uma requisição autenticada.
Os perfis já chegam ao contexto, mas isso não significa autorização completa. Regras como “somente ADMIN pode excluir” ainda precisam ser implementadas.
var authorities = List.of(
new SimpleGrantedAuthority(
"ROLE_" + usuario.getPerfil().name()));
var authentication =
new UsernamePasswordAuthenticationToken(
usuario.getEmail(), null, authorities);
authentication.setDetails(Map.of(
"usuarioId", usuario.getId(),
"imobiliariaId", usuario.getImobiliariaId()));
SecurityContextHolder.getContext()
.setAuthentication(authentication);
11 · Isolamento multi-tenant
Arquivo: backend/src/main/java/br/fatec/imobfiscal/security/JwtAuthenticationFilter.java
O filtro reconhece rotas com /api/imobiliarias/{id} e compara o UUID da URL com o tenant do token. A verificação central reduz o risco de um controller esquecer esse controle.
Quando os valores divergem, o backend retorna 403. Novos endpoints precisam manter o padrão de URL ou adotar verificação equivalente na camada de serviço.
Matcher matcher = ROTA_IMOBILIARIA.matcher(
request.getRequestURI());
if (matcher.matches()
&& !imobiliariaDoToken.equals(
UUID.fromString(matcher.group(1)))) {
escreverErro(response, HttpStatus.FORBIDDEN,
"Acesso negado aos dados de outra imobiliária");
return;
}
12 · Bean Validation
Arquivo: backend/src/main/java/br/fatec/imobfiscal/model/ContratoRequest.java
As anotações verificam ausência, formato, valor mínimo e faixa do vencimento. Quando o controller recebe o DTO com @Valid, uma violação resulta em 400 e o manipulador global organiza os erros por campo.
O CPF ou CNPJ ainda é conferido apenas por tamanho e formato. A validação dos dígitos verificadores permanece como melhoria futura.
@NotNull(message = "Imóvel obrigatório")
UUID imovelId,
@NotBlank
@Size(min = 11, max = 14,
message = "CPF ou CNPJ inválido")
@Pattern(regexp = "\\d{11}|\\d{14}",
message = "CPF/CNPJ deve conter 11 ou 14 números")
String locatarioCpfCnpj,
@NotNull
@DecimalMin(value = "0.01",
message = "Valor do aluguel deve ser positivo")
BigDecimal valorAluguel,
@NotNull @Min(1) @Max(31)
Integer diaVencimento
13 · Axios
Arquivo: frontend/src/services/api.js
O interceptor lê o token armazenado pelo AuthContext e monta o cabeçalho em todas as chamadas feitas por essa instância. O frontend também usa a imobiliária retornada no login nas rotas autenticadas.
O cadastro público ainda utiliza um tenant padrão. Além disso, o armazenamento em localStorage exige prevenção de XSS; cookies HttpOnly podem ser avaliados em uma evolução da arquitetura.
api.interceptors.request.use((config) => {
const token = localStorage.getItem(
'imobfiscal_token')
if (token) {
config.headers.Authorization =
`Bearer ${token}`
}
return config
})
14–15 · Testes e resultados
A suíte foi rodada com o código fornecido e confirmou todos os 12 cenários sem falhas ou erros.
Os testes usam JUnit 5, MockMvc e o perfil test. O H2 cria um banco temporário em memória no modo de compatibilidade com MySQL. Isso permite testar o fluxo de forma rápida e repetível, mas não transforma o H2 em banco de produção nem elimina a necessidade futura de testes complementares com MySQL ou MariaDB.
401 · Sem token
mockMvc.perform(get(
"/api/imobiliarias/{id}/imoveis",
IMOBILIARIA_ID))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.message")
.value("Autenticação necessária"));O teste confirma que uma chamada anônima não alcança mais o recurso protegido.
403 · Outro tenant
mockMvc.perform(get(
"/api/imobiliarias/{id}/imoveis",
outraImobiliaria)
.header("Authorization",
"Bearer " + token))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.message")
.value("Acesso negado aos dados de outra imobiliária"));O token é válido, mas a autorização falha porque o tenant da URL não pertence ao usuário.
| Grupo de testes | Quantidade | Verificação principal |
|---|---|---|
JwtServiceTest | 2 | Geração, claims, expiração e recusa de chave curta. |
ValidationTest | 2 | Rejeição de login, CEP, UF e valores inválidos. |
SecurityIntegrationTest | 5 | Health público, 401, login, próprio tenant, 403 e erros 400. |
MotorTributarioTest | 3 | Regressão do cálculo, alíquota ausente e isenção. |
| Total | 12 | Todos aprovados. |
| Código HTTP | Situação no ImobFiscal |
|---|---|
| 200 OK | Login válido, health check ou consulta autenticada permitida. |
| 201 Created | Cadastro público concluído. |
| 400 Bad Request | DTO com campos ausentes ou inválidos. |
| 401 Unauthorized | Token ausente em rota protegida, inválido, expirado ou incompatível com o usuário. |
| 403 Forbidden | Token válido tentando acessar outra imobiliária. |
| 409 Conflict | E-mail já cadastrado. |
| 500 Internal Server Error | Falha inesperada tratada pelo manipulador global. |
16–17 · Limitações e próximos passos
Segurança não termina com a emissão do JWT. As limitações registradas orientam o backlog técnico.
localStorage exige proteção contra XSS.18 · Conclusão
A atividade transformou uma indicação visual de login em um fluxo que o servidor consegue verificar e testar.
Na versão anterior, o frontend tratava um UUID aleatório como token, mas a API não reconhecia aquela identidade. Com Spring Security e JWT, cada requisição protegida passa por validação de assinatura, expiração, existência do usuário e compatibilidade do tenant. BCrypt preserva o tratamento adequado das senhas e Bean Validation reduz a entrada de dados inconsistentes.
O resultado adiciona uma camada real de proteção ao ImobFiscal e torna o comportamento observável por códigos HTTP e testes automatizados. Ao mesmo tempo, a análise crítica mostra que segurança é um processo contínuo. Revogação, autorização granular, política de senha, auditoria, proteção do frontend e testes adicionais ainda devem evoluir. O principal aprendizado foi compreender que autenticação, autorização, validação e isolamento de dados precisam trabalhar em conjunto.
Evidências
19 · Referências
Foram priorizadas especificações e documentações oficiais, com acesso em 31 de outubro de 2026.