Portfólio DSM ← Voltar ao PI

Desenvolvimento Web III · Projeto Integrador

Segurança no
ImobFiscal.

Implementação de autenticação stateless, JWT, validação de entrada e isolamento dos dados por imobiliária.

01

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

Uma tela de login não protegia a API.

O fluxo anterior parecia autenticado no navegador, mas o backend não reconhecia de forma confiável a identidade apresentada.

Contextualização da atividade

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.

Antes

Proteção apenas aparente

  • UUID aleatório usado como token.
  • Cabeçalho enviado, mas não validado.
  • Rotas da API abertas.
  • Tenant informado na URL sem conferência central.
  • Validações incompletas em alguns DTOs.
Objetivo

Proteção verificável

  • JWT assinado e com expiração.
  • Autenticação exigida em cada rota privada.
  • Tenant comparado com usuário, token e URL.
  • Respostas 400, 401, 403 e 409 padronizadas.
  • Testes automatizados do fluxo completo.
Situação anteriorRisco associado
Login retornava UUID aleatórioO valor não podia comprovar a identidade nem ser validado criptograficamente.
Authorization não era conferidoUma chamada direta à API podia ignorar o fluxo de login.
Rotas /api/** abertasRecursos de locadores, imóveis, contratos e cobranças não exigiam autenticação.
imobiliariaId recebido pela URLUm usuário poderia tentar trocar o identificador para consultar outro tenant.
Ausência de controle centralVerificações repetidas em controllers poderiam ser esquecidas ou aplicadas de modo desigual.

5 · Fundamentação teórica

Sete conceitos que trabalham juntos.

Autenticação, autorização e validação resolvem problemas diferentes. JWT e BCrypt são ferramentas dentro dessa arquitetura, não soluções isoladas.

5.1

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.

5.2

Spring Security

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.

5.3

JWT

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.

5.4

BCrypt

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.

5.5

API stateless

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.

5.6

Bean Validation

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.

5.7

Isolamento multi-tenant

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

Da senha digitada ao controller protegido.

O login produz uma credencial assinada. As requisições seguintes passam por uma nova verificação antes de alcançar as regras do sistema.

01Usuário e ReactInforma e-mail e senha
→
02AuthControllerRecebe POST /api/auth/login
→
03BCryptCompara senha e hash
→
04JwtServiceAssina o JWT
→
05AxiosEnvia Bearer token
→
06Filtro JWTValida identidade e tenant
→
07ControllerProcessa a rota autorizada
  1. Bearer?

    Sem token, uma rota pública continua. Uma rota protegida termina em 401.

  2. Válido?

    Assinatura inválida ou token expirado resulta em 401.

  3. Usuário?

    O filtro recarrega o cadastro. Usuário inexistente ou tenant incompatível resulta em 401.

  4. Tenant?

    Se o UUID da URL for de outra imobiliária, a resposta é 403.

  5. Entrada?

    Campos que violam as anotações de validação retornam 400.

  6. Aprovado

    Com todas as verificações aprovadas, o controller realiza o processamento normal.

Tecnologias utilizadas

Uma solução integrada do navegador ao banco.

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

Trechos reais da implementação.

Os exemplos foram reduzidos para destacar a responsabilidade de cada arquivo. Nenhuma senha, chave ou token real é exibido.

8 · SecurityFilterChain

Configuração central das rotas

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

Geração do token assinado

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

Assinatura e expiração conferidas

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

Leitura do cabeçalho Bearer

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

Identidade reconhecida pelo Spring

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

Tenant do token comparado com a URL

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

Entrada validada antes da regra de negócio

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

Bearer token enviado automaticamente

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

O comportamento foi executado, não apenas descrito.

A suíte foi rodada com o código fornecido e confirmou todos os 12 cenários sem falhas ou erros.

BUILDSUCCESS
Tests run
12
Failures
0
Errors
0
Skipped
0

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

Rota privada bloqueada

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

Imobiliária diferente bloqueada

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 testesQuantidadeVerificação principal
JwtServiceTest2Geração, claims, expiração e recusa de chave curta.
ValidationTest2Rejeição de login, CEP, UF e valores inválidos.
SecurityIntegrationTest5Health público, 401, login, próprio tenant, 403 e erros 400.
MotorTributarioTest3Regressão do cálculo, alíquota ausente e isenção.
Total12Todos aprovados.
Código HTTPSituação no ImobFiscal
200 OKLogin válido, health check ou consulta autenticada permitida.
201 CreatedCadastro público concluído.
400 Bad RequestDTO com campos ausentes ou inválidos.
401 UnauthorizedToken ausente em rota protegida, inválido, expirado ou incompatível com o usuário.
403 ForbiddenToken válido tentando acessar outra imobiliária.
409 ConflictE-mail já cadastrado.
500 Internal Server ErrorFalha inesperada tratada pelo manipulador global.

16–17 · Limitações e próximos passos

A camada reduz riscos, mas ainda precisa evoluir.

Segurança não termina com a emissão do JWT. As limitações registradas orientam o backlog técnico.

Limitações atuais

  • Logout não revoga imediatamente um JWT já emitido.
  • Perfis entram no contexto, mas ainda não controlam cada operação.
  • Cadastro público ainda usa um tenant padrão no frontend.
  • Política mínima de senha é simples para uso real.
  • JWT armazenado em localStorage exige proteção contra XSS.
  • Isolamento depende do padrão atual das URLs.
  • H2 não reproduz todas as particularidades do banco de produção.

Trabalhos futuros

  • Validar dígitos de CPF e CNPJ e coerência entre datas.
  • Controlar transições de status.
  • Autorizar por perfil e operação.
  • Implementar revogação e refresh token.
  • Bloquear tentativas repetidas e aplicar rate limiting.
  • Criar recuperação de senha e confirmação de e-mail.
  • Fortalecer política de senha e auditoria.
  • Adicionar unicidade por imobiliária e mais testes de segurança.

18 · Conclusão

Fazer login e proteger a API são coisas diferentes.

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.

19 · Referências

Documentação técnica consultada.

Foram priorizadas especificações e documentações oficiais, com acesso em 31 de outubro de 2026.

  1. IETF. RFC 7519 — JSON Web Token. RFC Editor, 2015.
  2. Jakarta EE. Jakarta Bean Validation 3.0 Specification. Eclipse Foundation.
  3. JJWT. Java JWT Documentation.
  4. JUnit Team. JUnit 5 User Guide.
  5. OWASP Foundation. Authentication Cheat Sheet.
  6. OWASP Foundation. Password Storage Cheat Sheet.
  7. Spring. Spring Boot Reference Documentation 3.3.
  8. Spring. Spring Security Reference.