Tema
Autenticação e autorização
← Índice da documentação da API
Tudo nesta página foi conferido em
config/SecurityConfig.java,config/JwtAuthenticationFilter.java,config/JwtSecurityConfig.java,config/LoginRateLimitingFilter.java,realtime/WebSocketConfig.java,mes/MesAccess.java,mes/MesEdgeSecurity.java,service/AuthenticationService.javae nas anotações@PreAuthorizede cada controller.
1. Visão geral
A API é stateless: não há sessão de servidor (SessionCreationPolicy.STATELESS) e o CSRF está desligado, coerente com um cliente que envia o token em toda chamada. Existem duas cadeias de segurança independentes:
| Cadeia | Alcance | Como autentica |
|---|---|---|
Principal (SecurityConfig) | toda a API, exceto /api/mes/edge/** | JWT no cabeçalho Authorization: Bearer <token> |
Edge do MES (MesEdgeSecurity, @Order(1)) | /api/mes/edge/** | chave compartilhada no cabeçalho X-Edge-Key |
São cadeias separadas de propósito: o coletor de chão de fábrica não é uma pessoa e não faz login.
2. Fluxo de autenticação (usuários)
Cliente API
| POST /api/auth/login {login, senha} |
|--------------------------------------->| normaliza login (trim + minúsculas)
| | busca usuário (case-insensitive)
| | se inativo → 403 "Usuário inativo. Procure um administrador."
| | confere senha (BCrypt); falha → 401 "Usuário ou senha inválidos."
| 200 UsuarioResponseDTO {token, perfilResumo, ...}
|<---------------------------------------|
| GET /api/qualquer-rota |
| Authorization: Bearer <token> |
|--------------------------------------->| JwtAuthenticationFilter:
| | 1. extrai e valida assinatura/expiração
| | 2. recarrega o usuário do banco; exige ativo = true
| | 3. recarrega o PERFIL do banco; exige perfil ativo
| | 4. monta as authorities (ver seção 4)
| resposta |
|<---------------------------------------|Pontos confirmados no código:
- Rotas de login: POST
/api/auth/logine POST/api/usuarios/login(mesma operação, dois caminhos). - Corpo do login:
{"login": "...", "senha": "..."}(LoginDTO, validado com@Valid). - Sessão atual: GET
/api/auth/medevolve o usuário autenticado sem token novo, comperfilResumo. - Token: JWT assinado com HS256. O
subjecté o login do usuário. Não há claims de perfil nem de permissões no token. Validade padrão:28800000ms (8 horas), configurável pela variávelSECURITY_JWT_EXPIRATION_MS. - Segredo:
SECURITY_JWT_SECRET(ouJWT_SECRET); o serviço não sobe sem ele nem com menos de 32 caracteres. A chave efetiva é o SHA-256 do segredo informado. - Não existe refresh token, logout no servidor, revogação de token nem troca de senha obrigatória. "Sair" é apenas descartar o token no cliente (ver documento interno de lacunas).
- Efeito imediato de mudanças: como usuário e perfil são relidos do banco a cada requisição, inativar um usuário, inativar um perfil ou mudar as telas de um perfil vale na próxima chamada, sem invalidar tokens.
- Perfil ausente, inativo ou desconhecido gera zero authorities: o usuário continua autenticado, mas toda rota que exige uma permissão específica responde
403.
3. Rotas públicas (sem token)
Declaradas em SecurityConfig:
| Rota | Observação |
|---|---|
OPTIONS /** | pré-voo de CORS |
POST /api/auth/login, POST /api/usuarios/login | login (limitado a 10 tentativas por minuto por IP) |
POST /api/usuarios | autocadastro (limitado a 5 por minuto por IP) — ver seção 6 |
/swagger-ui.html, /swagger-ui/**, /v3/api-docs/**, /api-docs/** | só respondem se SWAGGER_ENABLED=true (padrão false) |
/actuator/**, /error | liberados na cadeia; o que o Actuator expõe depende da configuração de deploy (não verificada) |
/ws/**, /ws-native/** | handshake de WebSocket liberado; a autenticação é exigida no comando STOMP CONNECT (seção 7) |
Todo o resto cai em anyRequest().authenticated() (sem token → 401).
4. Autorização: perfil, telas e tipo de acesso
O modelo é o de perfil (perfil): cada usuário tem um código de perfil (campo perfil do usuário, texto, não é chave estrangeira) que aponta para um registro com:
tipoAcesso:FULL_ACCESS(pode alterar) ouREAD_ONLY(só consulta);telasPermitidas: lista de "telas" (ex.:WAREHOUSE,PRODUCTS,MES_PCP);ativo.
O filtro JWT transforma isso em duas famílias de authorities:
| Authority | Origem |
|---|---|
ROLE_FULL_ACCESS ou ROLE_READ_ONLY | tipoAcesso do perfil |
SCREEN_<TELA> | uma por item de telasPermitidas (maiúsculas) |
As anotações @PreAuthorize combinam os dois eixos. O padrão que se repete é:
- leitura: exige só
SCREEN_<tela>; - criar / alterar / excluir: exige
SCREEN_<tela>ehasRole('FULL_ACCESS').
Exemplo real (AreaEstoqueController): GET /api/areas aceita SCREEN_DASHBOARD ou SCREEN_WAREHOUSE; POST /api/areas e DELETE /api/areas/{id} exigem SCREEN_WAREHOUSE e FULL_ACCESS.
4.1 Telas (SCREEN_*) e o que liberam
| Authority | Tela / uso no frontend | O que o backend exige |
|---|---|---|
SCREEN_DASHBOARD | Dashboard | leitura de setores (/api/areas) e do estoque consolidado (/api/estoque/itens, /api/estoque/itens-consolidado), sempre "ou" SCREEN_WAREHOUSE |
SCREEN_WAREHOUSE | Armazém, Setores, estruturas, itens de estoque, listas (encerto) | leitura com a tela; criar/alterar/excluir com a tela e FULL_ACCESS |
SCREEN_PRODUCTS | Produtos e categorias; Moldes | leitura com a tela; escrita com a tela e FULL_ACCESS |
SCREEN_MOLDS | Moldes | aceita-se SCREEN_MOLDS ou SCREEN_PRODUCTS nas rotas de moldes |
SCREEN_USERS | Usuários | listar/consultar com a tela; alterar com a tela e FULL_ACCESS |
SCREEN_PROFILES | Perfis | criar/alterar/ativar/inativar com a tela e FULL_ACCESS; listar também aceita SCREEN_USERS |
SCREEN_HISTORY | Histórico | consulta; o histórico por entidade também aceita SCREEN_USERS, SCREEN_PROFILES e SCREEN_PRODUCTS |
SCREEN_REPORTS | Relatórios de estoque (tela desativada no menu atual) | consulta |
SCREEN_ALERTS | Alertas (tela desativada no menu atual) | consulta |
SCREEN_MOVEMENTS | Movimentações (tela desativada no menu atual) | consulta; registrar exige também FULL_ACCESS |
Cada ficha de endpoint em modulos/ traz a expressão exata. A lista dos perfis iniciais está na seção 5.
4.2 MES / PCP: outro esquema
MesController não usa @PreAuthorize("SCREEN_...") direto. Usa o bean mesAccess (mes/MesAccess.java), que olha quatro authorities de tela e o papel FULL_ACCESS:
| Verificação | Regra no código |
|---|---|
read | qualquer uma entre SCREEN_MES_ADMIN, SCREEN_MES_OPERATOR, SCREEN_MES_MECHANIC, SCREEN_MES_PCP |
admin | FULL_ACCESS e SCREEN_MES_ADMIN |
operate | admin ou (FULL_ACCESS e SCREEN_MES_OPERATOR) |
maintain | admin ou (FULL_ACCESS e SCREEN_MES_MECHANIC) |
plan | admin ou (FULL_ACCESS e SCREEN_MES_PCP) |
Consequência importante: um perfil READ_ONLY com MES_PCP só consulta — não planeja. As fichas do módulo producao.md indicam qual verificação cada rota usa; GET /api/mes/permissions devolve {read, operate, maintain, plan, admin} do usuário logado, e é o que o frontend usa para mostrar/ocultar ações.
4.3 Autenticação do Edge (coletor industrial)
MesEdgeSecurity cria uma cadeia própria para /api/mes/edge/** que compara o cabeçalho X-Edge-Key com a variável de ambiente MES_EDGE_KEY (comparação em tempo constante, MessageDigest.isEqual):
| Situação | Resposta |
|---|---|
MES_EDGE_KEY ausente ou com menos de 16 bytes | 503 |
| cabeçalho ausente ou diferente da chave configurada | 401 |
| chave correta | segue para o controller (principal mes-edge, authority MES_EDGE) |
O corpo é {"message":"Integracao edge indisponivel ou credencial invalida"} nos dois casos — deliberadamente igual, para não revelar qual deles ocorreu. JWT de usuário não substitui a chave, e a chave não abre nenhuma rota fora de /api/mes/edge/**. A chave é global (não por máquina).
5. Perfis iniciais (migração V2__insert_default_data.sql)
| Código | Tipo | Telas |
|---|---|---|
ADMINISTRADOR | FULL_ACCESS | DASHBOARD, ALERTS, MOVEMENTS, REPORTS, WAREHOUSE, PRODUCTS, USERS, HISTORY, PROFILES |
OPERADOR | FULL_ACCESS | DASHBOARD, ALERTS, MOVEMENTS, REPORTS, WAREHOUSE, PRODUCTS, HISTORY |
CONSULTOR | READ_ONLY | DASHBOARD, ALERTS, REPORTS, WAREHOUSE, PRODUCTS, HISTORY |
Os perfis podem ser editados depois pela API/tela de Perfis (/api/perfis); o banco de produção pode estar diferente da semente. Essa semente não contém MOLDS nem as telas MES_*; elas passam a existir quando um perfil é criado ou editado com elas. A tela MOLDS foi criada depois: o frontend libera Moldes por MOLDS ou PRODUCTS, e o backend também aceita SCREEN_PRODUCTS ou SCREEN_MOLDS nas rotas de moldes.
6. Autocadastro: POST /api/usuarios
A rota é pública (usada pela tela "Criar conta"), mas o campo perfil do corpo só é respeitado quando quem chama é um usuário autenticado com SCREEN_USERS e FULL_ACCESS (chamadorPodeEscolherPerfil). Nos demais casos o perfil é forçado para CONSULTOR (PERFIL_PADRAO_AUTOCADASTRO), qualquer que seja o valor enviado. Por isso a pergunta "um anônimo consegue se criar como administrador?" tem resposta não no código atual.
7. WebSocket / STOMP
Endpoints /ws (com SockJS) e /ws-native (WebSocket puro). O handshake é sempre aceito, mas o comando STOMP CONNECT exige JWT válido (cabeçalho Authorization/authorization/X-Authorization/token do frame STOMP, ou parâmetro ?token= no handshake); sem isso a conexão é rejeitada com SecurityException. Usuário inativo ou token inválido também são rejeitados. Detalhes dos tópicos em integracoes.md.
8. CORS
CorsConfig aceita apenas origens de navegador autorizadas (o frontend publicado, ambientes locais de desenvolvimento e hosts internos da Wester).
Métodos permitidos: GET, POST, PUT, PATCH, DELETE, OPTIONS; cabeçalhos permitidos apenas Authorization e Content-Type; credenciais habilitadas; cache de pré-voo de 3600 s. Um cliente de servidor (cURL, TUTOM, coletor) não é afetado por CORS; um navegador em outra origem só consegue chamar a API se a origem estiver nessa lista.
X-Edge-Keynão está na lista de cabeçalhos permitidos: o coletor deve chamar de servidor/dispositivo, não de navegador.
9. Limites de taxa
LoginRateLimitingFilter (em memória, por IP, janela deslizante):
| Rota | Limite |
|---|---|
POST /api/auth/login | 10 por minuto |
POST /api/usuarios/login | 10 por minuto |
POST /api/usuarios | 5 por minuto |
Excedido: 429 com {"status":429,"error":"Too Many Requests","message":"Muitas tentativas. Aguarde um momento antes de tentar novamente."}. O IP vem do primeiro valor de X-Forwarded-For quando existe, senão do endereço remoto. O contador não sobrevive a reinício e não é compartilhado entre instâncias.
10. Exemplos
bash
# 1) login — devolve o token em .token
curl -s -X POST "${BASE_URL}/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"login":"usuario@exemplo.com","senha":"<SENHA>"}'
# 2) chamada autenticada
curl -s "${BASE_URL}/api/auth/me" -H "Authorization: Bearer ${TOKEN}"
# 3) chamada do coletor (não usa JWT)
curl -s "${BASE_URL}/api/mes/edge/config?machineId=<TAG_DA_INJETORA>" -H "X-Edge-Key: ${EDGE_KEY}"Resposta típica de login (estrutura de UsuarioResponseDTO; valores ilustrativos):
json
{
"id": 12,
"login": "usuario@exemplo.com",
"nome": "Usuário Exemplo",
"perfil": "OPERADOR",
"ativo": true,
"token": "<JWT>",
"perfilResumo": {
"id": 2,
"code": "OPERADOR",
"description": "Operador",
"type": "FULL_ACCESS",
"allowedScreens": ["DASHBOARD", "ALERTS", "MOVEMENTS", "REPORTS", "WAREHOUSE", "PRODUCTS", "HISTORY"],
"active": true
}
}Exemplos acima não carregam credenciais nem endereços reais de produção.