Cada API REST exposta é uma porta aberta. Se essa porta não tem cadeado, trancou ou câmera, alguém vai entrar. No contexto Protheus, uma API insegura pode vazar dados financeiros, estoque ou dados sensíveis de clientes.
Autenticação: quem está acessando
A primeira camada de segurança é saber quem está fazendo a requisição. Existem três níveis de autenticação:
Basic Auth (não use em produção)
// O problema: credenciais em Base64 (não é criptografia!) // Header: Authorization: Basic dXNlcjpwYXNzd29yZA== // ADVPL — Validação Basic Auth (NÃO recomendado) // Dentro do WsMethod: If !ValBasicAuth(::GetHeader("Authorization")) ; Return .F. ; EndIf Static Function ValBasicAuth(cAuth) Local cUser, cPass If Empty(cAuth) .Or. Upper(Left(cAuth, 6)) != "BASIC " SetRestFault(401, "Autenticação necessária") Return .F. EndIf cAuth := Decode64(SubStr(cAuth, 7)) // Decodifica Base64 cUser := Left(cAuth, At(":", cAuth) - 1) cPass := SubStr(cAuth, At(":", cAuth) + 1) // ⚠ Credenciais em Base64 na rede! // ⚔ Usar apenas em desenvolvimento local, sempre sobre HTTPS Return ValidaUsuario(cUser, cPass)
API Keys (uso interno)
// API Key é uma string fixa gerada no servidor // Header: X-API-Key: ak_live_1234567890abcdef // ADVPL — Validação de API Key Static Function ValApiKey(cKey) Local cKeyValida := SuperGetMv("ES_APIKEY", .F., "") // Regra: customização usa prefixo ES_, nunca MV_ (reservado ao padrão TOTVS) If Empty(cKey) .Or. cKey != cKeyValida SetRestFault(401, "API Key inválida") Return .F. EndIf Return .T.
OAuth2 / TOTVS Auth (recomendado)
// O cliente obtém um token via /auth/token com credenciais // Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIs... // ADVPL — Validação Bearer Token Static Function ValBearerToken(cAuth) Local cToken := "" Local cSecret := SuperGetMv("ES_JWT_SECRET", .F., "") If Empty(cAuth) .Or. Upper(Left(cAuth, 7)) != "BEARER " SetRestFault(401, "Token não fornecido") Return .F. EndIf cToken := SubStr(cAuth, 8) // JWT exige lib própria (HMAC + validação de exp/iss) — // NÃO existe JwtVerify() nem DateUnix() nativos do ADVPL. If !JwtValidarLib(cToken, cSecret) // wrapper da sua lib JWT SetRestFault(401, "Token inválido ou expirado") Return .F. EndIf // Identidade do usuário: propague por retorno/parâmetro — // NUNCA em variável global (AppServer é multi-thread: // um usuário herdaria a identidade de outro) Return .T.
Para APIs internas (microserviços Protheus entre si), API Key é suficiente. Para APIs expostas para apps externos ou parceiros, use OAuth2/TOTVS Auth com tokens JWT.
HTTPS obrigatório
Transmitir dados sem HTTPS é como mandar uma carta aberta pelo correio — qualquer um lê no caminho. TLS não é opcional.
O REST do Protheus roda no HTTP embutido do AppServer — o Apache não é o servidor da API. O TLS é configurado no AppServer (seções [HTTPVHOST]/SSL do AppServer.ini) ou no proxy reverso. O Apache abaixo entra apenas se houver proxy na frente do AppServer — configurar o servidor errado é erro clássico.
# httpd.conf ou .htaccess # Forçar HTTPS em todas as rotas <VirtualHost *:80> ServerName api.elptecnologia.com.br Redirect permanent / https://api.elptecnologia.com.br/ </VirtualHost> <VirtualHost *:443> ServerName api.elptecnologia.com.br SSLEngine on SSLCertificateFile /etc/ssl/certs/api.pem SSLCertificateKeyFile /etc/ssl/private/api.key # Headers de segurança Header always set Strict-Transport-Security "max-age=63072000" Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "DENY" </VirtualHost>
Validação de entrada
Nunca confie no que vem do cliente. Tudo que o frontend envia deve ser validado no backend — sem exceção.
Static Function ValidarEntrada(cBody) Local oBody := JsonObject():New() // 1. Parse do JSON — body chega via ::GetContent() no WsMethod If !oBody:FromJson(cBody) SetRestFault(400, "JSON inválido") Return NIL EndIf // 2. Campos obrigatórios If Empty(oBody["codigo"]) .Or. Empty(oBody["descricao"]) SetRestFault(400, "Campos 'codigo' e 'descricao' são obrigatórios") Return NIL EndIf // 3. Tipos e tamanhos — validação real, não blacklist If Len(AllTrim(oBody["codigo"])) > 15 SetRestFault(400, "Campo 'codigo' máximo 15 caracteres") Return NIL EndIf If ValType(oBody["preco"]) != "N" .Or. oBody["preco"] < 0 SetRestFault(400, "Campo 'preco' deve ser numérico positivo") Return NIL EndIf // 4. SQL Injection: proteção estrutural, não busca de palavras. // Blacklist ("DROP/DELETE/INSERT" na descrição) é anti-padrão: // bypass com comentários/minúsculas e falso-positivo em nome próprio. // Nunca concatene entrada em query — query parametrizada via // FWExecStatement/ChangeQuery + validação de tipo/tamanho/whitelist. Return oBody
No WsMethod, o body chega via ::GetContent() e a função acima é chamada assim: oBody := ValidarEntrada(::GetContent()). Erros de validação respondem com SetRestFault(400, ...) + Return .F. no método.
Rate Limiting
Limitar requisições previne ataques de força bruta e abuso de recursos. Implemente um contador por IP com janela real de minuto e lock entre threads — o AppServer é multi-thread e contador global sem lock sofre corrida (dois requests incrementam ao mesmo tempo e o contador vaza):
Static Function CheckRateLimit(cIp) Local cKey := "RATE_" + cIp + "_" + Left(Time(), 5) // janela: minuto Local nCount := 0 Local nLimite := 100 // 100 req/minuto // Incrementa com lock — GetGlbValue/PutGlbValue com GlbLock/GlbUnlock GlbLock() nCount := Val(GetGlbValue(cKey)) + 1 PutGlbValue(cKey, cValToChar(nCount)) GlbUnlock() If nCount > nLimite SetRestFault(429, "Limite de requisições excedido (retry em 60s)") Return .F. EndIf Return .T.
Globais (GetGlbValue/PutGlbValue) não expiram — a chave com o minuto cria a janela, mas a memória cresce. Para produção séria, prefira rate limiting no proxy/API gateway ou cache com expiração real. E o IP do cliente chega ao AppServer do proxy — use o header X-Forwarded-For validado quando houver proxy.
CORS: quem pode acessar sua API
Cross-Origin Resource Sharing controla quais domínios podem fazer requisições à sua API. Configure com cuidado — e no lugar certo: o CORS nativo do Protheus fica no AppServer.ini ([HTTPURI] CORSEnable=1 + AllowOrigin=...); o bloco Apache abaixo vale somente quando há proxy reverso:
# Permitir apenas domínios específicos <Location /api/> Header always set Access-Control-Allow-Origin "https://app.elptecnologia.com.br" Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key" Header always set Access-Control-Max-Age "86400" # Preflight OPTIONS RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] </Location> # ⚠ NUNCA use: Access-Control-Allow-Origin: * # Isso permite qualquer site acessar sua API
Logging e auditoria
Registre quem acessou, o quê, quando e de onde. Mas nunca registre dados sensíveis. E a tabela de auditoria precisa de índice, ordem e política de retenção — auditoria sem purga cresce infinito e vira scan.
Static Function LogAcesso(cEndpoint, cMetodo, cUsuario, cIp) // Área + ordem corretas antes de gravar (índice: ZA1_FILIAL+ZA1_DATA+ZA1_HORA) dbSelectArea("ZA1") ZA1->(DbSetOrder(1)) RecLock("ZA1", .T.) ZA1->ZA1_DATA := Date() ZA1->ZA1_HORA := Time() ZA1->ZA1_USR := cUsuario ZA1->ZA1_IP := cIp ZA1->ZA1_ENDPNT := cEndpoint ZA1->ZA1_METODO := cMetodo MsUnLock() // ⚠ NUNCA registre: senhas, tokens, dados bancários // ✅ Registre: quem, quando, de onde, qual endpoint // Complemente com FWLogMsg (CA1004) e defina retenção/purge // da tabela (ex.: purga mensal de ZA1 > 12 meses) Return Nil
A identidade do usuário (cUsuario) chega como parâmetro — nunca como variável global não declarada: em AppServer multi-thread, estado de request em global faz um usuário herdar a identidade de outro. E confira no SX3 o tamanho de ZA1_ENDPNT (campos de 10 caracteres truncam endpoints reais — dimensione o campo).
OWASP Top 10 no contexto Protheus
O OWASP Top 10 é a lista dos riscos de segurança mais críticos em aplicações web. Veja como cada um se aplica ao Protheus:
| OWASP | Risco no Protheus | Prevenção |
|---|---|---|
| Injection | SQL Injection via parâmetros não sanitizados | Parâmetros compostos, validação de entrada |
| Broken Auth | Tokens sem expiração, senhas fracas | JWT com expiração, MFA quando possível |
| Sensitive Data | Dados bancários ou CPF em logs | Mascaramento em logs, criptografia em trânsito |
| XXE | Parsing de XML malicioso (menos comum em REST) | Usar JSON, desabilitar parsing de XML externo |
| Broken Access | Acessar dados de outra empresa/filial | Validação de filial em cada query, RBAC |
| Security Misconfig | Debug ligado em produção, portas abertas | Hardening do Apache, desabilitar debug |
| Logging Failures | Sem audit trail, logs insuficientes | Auditoria obrigatória, retenção de logs |
Checklist de segurança
Sem exceção. HTTP é aceitável apenas em ambiente de desenvolvimento local.
Toda API que retorna dados ou altera estado deve exigir autenticação.
Nunca use tokens infinitos. Defina expiração curta conforme o risco, com refresh e revogação documentados (OWASP não fixa "24 horas" — meça o risco da sua API).
Tipo, tamanho, formato, whitelist. Nunca confie no que vem do cliente.
Limite de requisições por IP/token. Previne força bruta e abuso.
Apenas domínios conhecidos. Nunca use wildcard (*) em produção.
Log de quem, quando, o quê e de onde. Nunca registre senhas ou tokens.
HSTS, X-Content-Type-Options, X-Frame-Options — no proxy reverso se houver, ou no AppServer onde o TLS termina.
Stack trace e detalhes internos vão só para o log interno (ver artigo de debugging) — nunca na resposta HTTP.
Auditoria trimestral. APIs que foram seguras há 6 meses podem não ser hoje.
Conclusão
Segurança em APIs REST do Protheus não é opcional — é responsabilidade. Cada API insegura é um risco para a empresa, seus dados e seus clientes. A boa notícia é que as práticas são conhecidas e testadas: HTTPS, autenticação, validação, rate limiting e auditoria.
Comece pelo básico, automatize o que puder, e revise periodicamente. A segurança não é um projeto com fim — é um processo contínuo.
Precisa revisar a segurança das suas APIs Protheus?
Falar com a ELP