Introdução ao JWT
JWT (JSON Web Token) é um método conciso e autossuficiente de autenticação na web. Ele consiste em três partes: o cabeçalho, a carga útil e a assinatura. O cabeçalho contém o tipo do token e informações sobre o algoritmo de criptografia, a payload contém os dados a serem transmitidos (geralmente incluindo algumas informações de permissão e identificação do usuário) e a assinatura é usada para verificar a integridade e validade do token.
JWT é principalmente usado em dois cenários:
- Autenticação: Uma vez que um usuário faz login, cada solicitação subsequente incluirá um JWT, permitindo que o usuário acesse rotas, serviços e recursos permitidos.
- Troca de informações: O JWT é um bom método para transmitir informações de forma segura entre as partes, já que podem ser assinados digitalmente, por exemplo, usando pares de chaves pública/privada.
Formato dos Dados JWT
O JWT é uma forma compacta e segura de representar informações a serem trocadas entre as partes. Na verdade, um JWT consiste em três partes, separadas por pontos (.``):
Cabeçalho.Payload.Assinatura`. A seguir, detalharemos os formatos de dados dessas três partes.
1. Cabeçalho
O cabeçalho normalmente consiste em duas partes: o tipo do token, geralmente JWT
, e o algoritmo de assinatura ou criptografia usado, como HMAC SHA256
ou RSA
. O cabeçalho é representado em JSON e, em seguida, codificado em uma string usando Base64Url. Por exemplo:
{
"alg": "HS256",
"typ": "JWT"
}
Após a codificação, você pode obter uma string semelhante a esta:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload
A carga útil consiste em uma série de declarações, que são informações sobre a entidade (geralmente um usuário) e outros dados. A payload pode incluir várias declarações predefinidas (também conhecidas como Declarações Registradas) e declarações personalizadas (Declarações Privadas).
As declarações predefinidas podem incluir:
-
iss
(Emissor): Emissor -
exp
(Tempo de Expiração): Tempo de expiração -
sub
(Assunto): Assunto -
aud
(Público): Público -
iat
(Emitido em): Tempo de emissão -
nbf
(Não Antes de): Tempo efetivo -
jti
(ID do JWT): Identidade única do JWT
Um exemplo de payload pode ser assim (em formato JSON):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
Essas informações também serão codificadas com Base64Url, e você pode obter uma string semelhante a esta:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. Assinatura
A seção de assinatura é usada para assinar as duas strings codificadas mencionadas acima, a fim de verificar que a mensagem não foi alterada durante a transmissão. Primeiramente, você precisa especificar uma chave (se estiver usando o algoritmo HMAC SHA256) e, em seguida, usar o algoritmo especificado no cabeçalho para assinar o cabeçalho e a carga útil.
Por exemplo, se você tiver o seguinte cabeçalho e payload:
CabeçalhoCodificado.CargaÚtilCodificada
Um pseudocódigo para assiná-los usando uma chave poderia ser:
HMACSHA256(base64UrlEncode(cabeçalho) + "." + base64UrlEncode(cargaútil), chaveSecreta)
A string assinada resultante poderia ser:
dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
JWT Completo
Combinando essas três partes com um ponto (.) como separador forma o JWT completo:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
O padrão JWT é altamente flexível e pode armazenar qualquer informação necessária na payload (mas, com o objetivo de reduzir o tamanho do JWT e por motivos de segurança, informações sensíveis não devem ser armazenadas) e garante a integridade dessas informações por meio da assinatura.
Instalando a biblioteca JWT em Go
A comunidade Go fornece um pacote github.com/golang-jwt/jwt
para lidar com JWT. A instalação desta biblioteca é muito simples, basta executar o seguinte comando no diretório do seu projeto:
go get -u github.com/golang-jwt/jwt/v5
Uma vez instalada, você pode incluí-la em seu import
da seguinte forma:
import "github.com/golang-jwt/jwt/v5"
Criando um token simples
Para criar um token JWT simples usando a linguagem Go e o algoritmo HS256, você precisa seguir estes passos:
Primeiro, gere um novo objeto Token, usando o algoritmo HS256:
token := jwt.New(jwt.SigningMethodHS256)
Em seguida, use o método SignedString
para gerar uma representação de string deste token, passando a chave que você irá usar para assinar.
var minhaChaveDeAssinatura = []byte("seu-segredo-de-256-bits")
strToken, err := token.SignedString(minhaChaveDeAssinatura)
if err != nil {
log.Fatalf("Ocorreu um erro: %v", err)
}
fmt.Println(strToken)
Isso irá gerar um token simples sem nenhuma reivindicação.
Criando um token com parâmetros
Uma das principais funções do JWT é carregar informações. Estas informações são codificadas no Token através de reivindicações. Por exemplo, vamos criar reivindicações personalizadas:
// Estrutura de Reivindicações Personalizadas
type MinhasReivindicações struct {
jwt.RegisteredClaims
NomeDeUsuário string `json:"username"`
Admin bool `json:"admin"`
}
// Criar um token com reivindicações personalizadas
reivindicacoes := MinhasReivindicações{
RegisteredClaims: jwt.RegisteredClaims{},
NomeDeUsuário: "meu_nome_de_usuário",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, reivindicacoes)
// Chave secreta para assinar
var minhaChaveDeAssinatura = []byte("seu-segredo-de-256-bits")
// Gerar o token em formato de string
strToken, err := token.SignedString(minhaChaveDeAssinatura)
if err != nil {
log.Fatalf("Ocorreu um erro: %v", err)
}
fmt.Println(strToken)
No código acima, definimos a estrutura MinhasReivindicações
para conter reivindicações registradas, bem como algumas informações personalizadas. Em seguida, ainda usamos SignedString
para gerar a string do token.
Analisando e validando o token
Analisar e validar o JWT é muito importante e você pode fazer isso da seguinte maneira:
// Vamos usar a mesma estrutura MinhasReivindicações
tokenString := "sua-string-JWT"
// Precisamos definir uma função que o pacote jwt usará para analisar a tokenString
funcaoChave := func(t *jwt.Token) (interface{}, error) {
// Verificar se o método de assinatura esperado está sendo usado
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Método de assinatura inesperado: %v", t.Header["alg"])
}
// Retornar a chave secreta para a token jwt, no formato []byte, consistente com a chave usada para assinar anteriormente
return minhaChaveDeAssinatura, nil
}
// Analisando o token
reivindicacoes := &MinhasReivindicações{}
tokenAnalisado, err := jwt.ParseWithClaims(tokenString, reivindicacoes, funcaoChave)
if err != nil {
log.Fatalf("Erro de análise: %v", err)
}
if !tokenAnalisado.Valid {
log.Fatalf("Token inválido")
}
// Nesta fase, o tokenAnalisado foi verificado e podemos ler as reivindicações
fmt.Printf("Usuário: %s, Admin: %v\n", reivindicacoes.NomeDeUsuário, reivindicacoes.Admin)
No código acima, fornecemos a string do token, a instância MinhasReivindicações
e a função de chave funcaoChave
para a função jwt.ParseWithClaims
. Esta função irá validar a assinatura e analisar o token, preenchendo a variável reivindicacoes
se o token for válido.