Assinatura de Requisições
Nossa API utiliza assinatura de requisições baseada em JWT para autenticar e verificar a integridade das suas requisições. Este guia explica como implementar a assinatura de requisições em suas aplicações.
Visão Geral
Cada requisição para nossa API deve incluir um token JWT assinado no cabeçalho Authorization. O token contém declarações sobre a requisição e é assinado usando sua chave privada com o algoritmo RS256.
Preparar credenciais
Você precisa de duas credenciais:
Chave de API : Seu identificador único
Chave Privada : Sua chave privada RSA em formato PEM
Extrair caminho da URL
Extraia o caminho e os parâmetros de consulta da URL da requisição.
Não inclua o domínio ou protocolo nos dados assinados.
Gerar hash do corpo
Calcule um hash SHA-256 do corpo da requisição para garantir integridade.
Criar e assinar JWT
Crie um JWT com declarações específicas e assine-o com sua chave privada.
Adicionar cabeçalho Authorization
Inclua o JWT assinado no cabeçalho Authorization como um token Bearer.
Implementação
Abaixo está uma implementação em TypeScript que você pode usar como referência para outras linguagens:
import * as jwt from 'jsonwebtoken' ;
import * as crypto from 'crypto' ;
import { URL } from 'url' ;
/**
* Assina uma requisição de API com JWT usando algoritmo RS256
* @param privateKey Sua chave privada RSA
* @param apiKey Sua chave de API
* @param requestUrl URL completa da requisição
* @param requestBody Corpo da requisição (string ou undefined)
* @returns String do token Bearer para o cabeçalho Authorization
*/
export function signRequest (
privateKey : string ,
apiKey : string ,
requestUrl : string ,
requestBody ?: string
) : string {
// Construir url da requisição - extrair caminho e consulta
const parsedUrl = new URL ( requestUrl );
const url = parsedUrl . pathname + parsedUrl . search ;
// Hash do corpo da requisição
const body = requestBody || '{}' ;
const bodyHash = crypto
. createHash ( 'sha256' )
. update ( body )
. digest ( 'hex' );
// Gerar o JWT assinado
const jwtHeader = {
typ: 'JWT' ,
alg: 'RS256'
};
const now = Math . floor ( Date . now () / 1000 );
const jwtData = {
uri: url ,
iat: now ,
exp: now + 55 ,
sub: apiKey ,
bodyHash: bodyHash
};
const signedJwt = jwt . sign ( jwtData , privateKey , {
algorithm: 'RS256' ,
header: jwtHeader
});
// Retorna valor do cabeçalho Authorization
return `Bearer ${ signedJwt } ` ;
}
Estrutura do Payload JWT
O payload JWT deve conter as seguintes declarações:
O caminho da URL com parâmetros de consulta (excluindo o domínio).
Exemplo: /v1/resources?filter=active
Horário de emissão como timestamp Unix (segundos desde a época).
Horário de expiração como timestamp Unix. Deve ser definido como iat + 55 segundos.
Hash SHA-256 do corpo da requisição como uma string hexadecimal.
Implementações em Linguagens Específicas
const jwt = require ( 'jsonwebtoken' );
const crypto = require ( 'crypto' );
const { URL } = require ( 'url' );
function signRequest ( privateKey , apiKey , requestUrl , requestBody ) {
// Extrair caminho com consulta
const parsedUrl = new URL ( requestUrl );
const url = parsedUrl . pathname + parsedUrl . search ;
// Hash do corpo
const body = requestBody || '{}' ;
const bodyHash = crypto . createHash ( 'sha256' ). update ( body ). digest ( 'hex' );
// Criar JWT
const now = Math . floor ( Date . now () / 1000 );
const payload = {
uri: url ,
iat: now ,
exp: now + 55 ,
sub: apiKey ,
bodyHash: bodyHash
};
const signedJwt = jwt . sign ( payload , privateKey , {
algorithm: 'RS256' ,
header: { typ: 'JWT' , alg: 'RS256' }
});
return `Bearer ${ signedJwt } ` ;
}
import time
import hashlib
from urllib.parse import urlparse
import jwt
def sign_request ( private_key , api_key , request_url , request_body = None ):
# Extrair caminho com consulta
parsed_url = urlparse(request_url)
url = parsed_url.path
if parsed_url.query:
url += '?' + parsed_url.query
# Hash do corpo
body = request_body if request_body else ' {} '
body_hash = hashlib.sha256(body.encode( 'utf-8' )).hexdigest()
# Criar JWT
now = int (time.time())
payload = {
'uri' : url,
'iat' : now,
'exp' : now + 55 ,
'sub' : api_key,
'bodyHash' : body_hash
}
headers = {
'typ' : 'JWT' ,
'alg' : 'RS256'
}
signed_jwt = jwt.encode(
payload = payload,
key = private_key,
algorithm = 'RS256' ,
headers = headers
)
return f "Bearer { signed_jwt } "
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public String signRequest ( String privateKeyPem, String apiKey, String requestUrl, String requestBody) throws Exception {
// Analisar URL
URL url = new URL (requestUrl);
String path = url . getPath ();
String query = url . getQuery ();
String uriPath = query != null ? path + "?" + query : path;
// Hash do corpo
String body = requestBody != null ? requestBody : "{}" ;
MessageDigest digest = MessageDigest . getInstance ( "SHA-256" );
byte [] hash = digest . digest ( body . getBytes ( StandardCharsets . UTF_8 ));
String bodyHash = bytesToHex (hash);
// Hora atual
long now = Instant . now (). getEpochSecond ();
// Cabeçalho JWT
Map < String , Object > header = new HashMap <>();
header . put ( "typ" , "JWT" );
header . put ( "alg" , "RS256" );
// Payload JWT
Map < String , Object > claims = new HashMap <>();
claims . put ( "uri" , uriPath);
claims . put ( "iat" , now);
claims . put ( "exp" , now + 55 );
claims . put ( "sub" , apiKey);
claims . put ( "bodyHash" , bodyHash);
// Converter PEM para PrivateKey
PrivateKey privateKey = loadPrivateKey (privateKeyPem);
// Assinar JWT
String signedJwt = Jwts . builder ()
. setHeader (header)
. setClaims (claims)
. signWith (privateKey, SignatureAlgorithm . RS256 )
. compact ();
return "Bearer " + signedJwt;
}
private static String bytesToHex ( byte [] hash) {
StringBuilder hexString = new StringBuilder ();
for ( byte b : hash) {
String hex = Integer . toHexString ( 0xff & b);
if ( hex . length () == 1 ) hexString . append ( '0' );
hexString . append (hex);
}
return hexString . toString ();
}
private static PrivateKey loadPrivateKey ( String privateKeyPem) throws Exception {
// Remover cabeçalhos e converter para binário
String privateKeyContent = privateKeyPem
. replace ( "-----BEGIN PRIVATE KEY-----" , "" )
. replace ( "-----END PRIVATE KEY-----" , "" )
. replaceAll ( " \\ s" , "" );
byte [] encoded = Base64 . getDecoder (). decode (privateKeyContent);
// Criar chave privada
KeyFactory keyFactory = KeyFactory . getInstance ( "RSA" );
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec (encoded);
return keyFactory . generatePrivate (keySpec);
}
<? php
use \Firebase\JWT\ JWT ;
function signRequest ( $privateKey , $apiKey , $requestUrl , $requestBody = null ) {
// Analisar URL
$urlParts = parse_url ( $requestUrl );
$path = $urlParts [ 'path' ] ?? '' ;
$query = isset ( $urlParts [ 'query' ]) ? '?' . $urlParts [ 'query' ] : '' ;
$url = $path . $query ;
// Hash do corpo
$body = $requestBody ?? '{}' ;
$bodyHash = hash ( 'sha256' , $body );
// Hora atual
$now = time ();
// Cabeçalhos JWT
$header = [
'typ' => 'JWT' ,
'alg' => 'RS256'
];
// Payload JWT
$payload = [
'uri' => $url ,
'iat' => $now ,
'exp' => $now + 55 ,
'sub' => $apiKey ,
'bodyHash' => $bodyHash
];
// Assinar JWT
$signedJwt = JWT :: encode ( $payload , $privateKey , 'RS256' , null , $header );
return 'Bearer ' . $signedJwt ;
}
?>
Testando Sua Implementação
Você pode verificar se sua implementação está funcionando corretamente:
Gerando uma requisição assinada
Decodificando o JWT em jwt.io para verificar as declarações
Fazendo uma requisição de teste para nosso endpoint de API
Certifique-se de que o corpo da requisição seja idêntico entre a geração da assinatura e a requisição real.
Mesmo uma única diferença de espaço em branco resultará em um hash diferente e falha na validação da assinatura.
Problemas Comuns
Falha na Verificação da Assinatura
Isso normalmente significa:
O formato da sua chave privada está incorreto
O hash do corpo não corresponde ao corpo da requisição real
O JWT expirou (deve ser válido apenas por 55 segundos)
Declarações Obrigatórias Ausentes
Certifique-se de que seu JWT inclua todas as declarações obrigatórias: uri, iat, exp, sub e bodyHash.
A declaração uri deve incluir apenas o caminho e a string de consulta, não o domínio ou protocolo.
Melhores Práticas de Segurança
Armazene sua chave privada com segurança e nunca a exponha em código do lado do cliente
Gere um novo JWT para cada requisição
Mantenha o tempo de expiração curto (55 segundos) para evitar ataques de repetição
Implemente tratamento adequado de erros para falhas de autenticação