Validando tokens JWT com JWKS, Micronaut e Kotlin
06 Dec 2021Introdução
Uma necessidade que aparece com frequência na construção de sistemas é garantir que o acesso de algumas informações só podem ser feitas após um usuário se identificar, geralmente ele faz isso usando um nome de usuário e senha. Após a validação das credenciais o sistema emite um identificador único para a sessão que foi criada. Usando esse identificador único, que é mais conhecido por token, é possível consultar informações que estão associadas ao usuário que iniciou aquela sessão.
Um token pode ser opaco como uma string aleatória, ou seja, não é possível identificar nenhuma informação útil a partir do token. Ou ele pode ser um token rico, como é o JWT, onde é possível ler informações de dentro do token.
O que é um token JWT?
JWT é um padrão que define uma maneira segura de encapsular informações e transmiti-las por um canal inseguro. Um token JWT é composto por 3 partes: cabeçalho, claims e assinatura. Veja um exemplo, cada parte do token é separada por um ponto:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Esse token pode ser visualizado em um site como o https://jwt.io conforme imagem abaixo:
No exemplo acima, o cabeçalho do token contém o algoritmo usado para assinar o token e o tipo do token, ele também pode conter um campo chamado kid
, que é o identificador da chave usada para assinar o token, caso esteja sendo usada criptografia assimétrica.
O corpo do token é formado por claims, que são chaves e valores com informações úteis, uma delas é a claim sub
que identifica o usuário e outra é a claim exp
que indica a data de expiração de um token.
A assinatura pode ser feita usando um segredo que só o servidor conhece (criptografia simétrica) ou pode ser feita usando um par de chaves (criptografia assimétrica). Ao usar um par de chaves, somente o servidor terá acesso à chave privada e as chaves públicas podem ser compartilhadas para que aplicações cliente possam validar se aquele token foi emitido pelo servidor.
O que é JWKS?
Um JWKS (Json Web Key Set) é um conjunto de chaves públicas que podem ser usadas para validar tokens JWT. Geralmente um JWKS fica exposto em um endereço público como https://www.servidor.com.br/.well-known/jwks
, e esse endereço retorna um array contendo todas as chaves públicas disponíveis. Veja um exemplo:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "ca761cd3-8092-46be-926b-ef28465ff942",
"n": "oXAP360uf_9_KXTCk6BiQOgwQJlqoycCbsukFtoUCmn57jM-9n2uqBBPT_8VnTIaYr4h8zxMy8HRkdX35HRmZANoqekhH03hhMc69mK4yEYZwBNyV9SteXrF5hfj4SWsK0t3CZ_G_U303XLj7ak5m-4w1UXCmvBERR_SwXjLOKwAAFlOQS_0sAB9yzvJkvsuvqd4lA3-vFFF_ZVbTHuJAznqB_avwCbCHJWfiWln2PN7LsieX08tE13bPP1TVEFid9mcUz5dwz0J9QKTYCd90fkyzqanzG638SFoyL84ddmD_9pef5x03oMWEU9-dxEI6PFfWEQmXN1eg7GfJI6bxQ"
}
]
}
Cada chave nessa lista possui um identificador único que é o mesmo kid
usado no cabeçalho do token JWT, esse kid
representa o identificador da chave pública que faz par com a chave privada que foi usada para assinar o token. Dessa forma é possível, a partir de um token JWT, encontrar a chave pública para validar a assinatura.
Validando tokens JWT com Micronaut
Imagine que você tem um microsserviço que expõe em http://localhost:8081/keys
a lista de chaves públicas que podem ser usadas para validar a assinatura de tokens JWT. Você tem uma API protegida que um usuário só pode ter acesso com um token JWT válido.
O primeiro passo é criar um projeto Micronaut adicionando a dependência security-jwt
. Acesse https://micronaut.io/launch/ e crie um novo projeto com a linguagem Kotlin e adicione a feature security-jwt
.
Crie o Controller
abaixo no seu projeto:
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.security.annotation.Secured
import io.micronaut.security.rules.SecurityRule.IS_AUTHENTICATED
@Controller
class HelloController {
@Get("/hello")
@Secured(IS_AUTHENTICATED) // 1
fun hello() = "Hello World"
}
Trata-se de um controller muito simples, ele vai retornar a mensagem Hello World
caso o usuário esteja autenticado, ou seja, tenha um token válido. Fazemos isso com a anotacão @Secured(IS_AUTHENTICATED)
. O próximo passo é informar ao Micronaut onde ele deve buscar a lista de chaves públicas. Para isso abra o arquivo application.yaml
e adicione as configurações abaixo:
micronaut:
application:
name: demo
security:
authentication: bearer
token:
jwt:
signatures:
jwks:
custom:
url: 'http://localhost:8081/keys'
Pronto! Agora basta subir sua aplicação e testar usando uma chamada parecida com o exemplo abaixo:
curl --location --request GET 'http://localhost:8080/hello' \
--header 'Authorization: Bearer eyJraWQiOiJkNThmMjM5Yy03YjcwLTRmMTktYjExNC0xZmZmYzhkNTgyYWYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwibmJmIjoxNjM4ODM3Mjc4LCJyb2xlcyI6WyJBRE1JTiJdLCJpc3MiOiJtaWNyb25hdXQtc2VjdXJpdHktand0LXNhbXBsZSIsImV4cCI6MTYzODg0MDg3OCwiaWF0IjoxNjM4ODM3Mjc4fQ.O-uTW_HkDegCoFClUfv2zpQsPDM4FEpBoyTw9y2pLjibnYLnr8BtxYhXZ8y6Rzx0fyTxwY3nJe3PSMwv71tEHbW8qRGCSt8J_lsWkohVrxHBM5HguECTeiMOnL6applQxtn8mCuN2Y3bsVGXpYtoTiUytTDb3zo0KiSWYEsendnwXo6hIvVQV5-HBbgXm6F26SZjrKgVr4Y1X_rQL-NcBsSMDzoiaZaLytnizfDjrcyzXDzCIc0JNEFS7HlBBZKT1R2cUqpcdj516idFv3nDxD6TPlNJgrXmUlgOfOg1id5FL_2pqa21HQioj4bdk0kQQuj2mxMIw4ZCALCYyD5Tfg'
Conclusão
Micronaut Security oferece uma maneira simples de validar tokens JWT em seus microsserviços. Basta indicar a url onde ficam as chaves públicas que ele se encarrega de fazer a validação.
Caso tenha dúvidas ou sugestões, utilize a caixa de comentários abaixo ou entre em contato pelo twitter em @john_owl