Roteamento Dinâmico Baseado em Credenciais de Usuário com API Gateway

Bobur Umurzokov

Bobur Umurzokov

April 9, 2023

Technology

Roteamento dinâmico baseado em reivindicação JWT com Apache APISIX e Okta

O roteamento dinâmico é um recurso poderoso da maioria dos gateways de API modernos que permite rotear solicitações recebidas em tempo real para diferentes serviços de backend com base em vários critérios, como cabeçalhos HTTP, parâmetros de consulta ou até mesmo o corpo da solicitação.

Ao aproveitar os plugins internos existentes do Apache APISIX, os desenvolvedores também podem criar regras de roteamento dinâmico baseadas em várias credenciais de usuário, como tokens de acesso, chaves de API ou IDs de usuário. Neste artigo, exploraremos os benefícios de adotar o roteamento dinâmico baseado em atributos de autenticação com o Apache APISIX e mostraremos um exemplo de configuração de como rotear dinamicamente as solicitações dos clientes para os serviços de backend responsáveis com base na reivindicação do token JWT.

Objetivos de aprendizagem

Você aprenderá o seguinte ao longo do artigo:

  • Roteamento dinâmico de tráfego com Gateway de API.
  • Por que precisamos de roteamento dinâmico baseado em credenciais de usuário?
  • Roteamento dinâmico baseado em reivindicação de token JWT com Apache APISIX.

Gateway de API: Roteamento dinâmico de tráfego

O roteamento dinâmico de tráfego com o Gateway de API pode ser usado em uma ampla gama de aplicações e cenários para otimizar o desempenho, melhorar a segurança e garantir que os usuários tenham acesso aos recursos apropriados.

Ao rotear dinamicamente o tráfego, um sistema pode balancear a carga entre diferentes servidores ou serviços. Isso pode ajudar a garantir alta disponibilidade, roteando o tráfego para serviços ou servidores disponíveis. Se um serviço ou servidor falhar, o tráfego pode ser automaticamente redirecionado para outro serviço ou servidor disponível.

roteamento dinâmico de tráfego com Apache APISIX

O roteamento dinâmico também pode ser usado para rotear o tráfego com base na geolocalização do usuário. Isso pode ajudar a garantir que os usuários sejam conectados ao servidor ou serviço mais próximo, melhorando os tempos de resposta e reduzindo a latência.

roteamento de tráfego com base na geolocalização com Apache APISIX

Gateway de API: Roteamento Dinâmico Baseado na Identidade do Usuário

Muitas vezes, queremos rotear o tráfego para serviços específicos, caminhos ou mostrar apenas dados relacionados ao usuário com base na identidade fornecida pelo usuário. Por exemplo, em aplicações multi-inquilino, diferentes inquilinos podem ter acesso a diferentes serviços ou recursos. Nesse caso, o Gateway de API pode rotear o tráfego apenas para os recursos apropriados do inquilino com base nas credenciais do usuário. Ou em aplicações móveis, pode rotear o tráfego para serviços específicos com base no tipo de dispositivo ou sistema operacional.

Uma das abordagens comuns é usar tokens JWT para autenticar e autorizar solicitações a APIs. Isso significa que podemos criar regras de roteamento complexas com o Gateway de API que levam em consideração as reivindicações presentes no token JWT e usam essas informações para decidir para onde encaminhar a solicitação ou quais dados mostrar. Essa abordagem é particularmente útil quando você tem vários usuários no sistema que exigem diferentes níveis de controle de acesso.

Roteamento dinâmico de tráfego com base no token JWT com Apache APISIX

Demonstração: Roteamento dinâmico baseado em reivindicação de token JWT

Nesta demonstração, usamos a API pública de backend chamada Conference API com informações sobre sessões, palestrantes e tópicos de conferências. Na realidade, pode ser o seu serviço de backend. Vamos supor que queremos filtrar e recuperar apenas as sessões pertencentes a um palestrante específico que está logado no sistema usando suas credenciais, como um token JWT. Por exemplo, https://conferenceapi.azurewebsites.net/speaker/1/sessions

A solicitação mostra apenas as sessões de um palestrante com um ID único, e esse ID único vem da reivindicação do token JWT como parte de seu payload. Veja a estrutura do payload do token decodificado abaixo, há um campo speakerId incluído:

Token JWT com uma reivindicação personalizada

Nesse cenário, enviamos solicitações para a mesma Rota no Gateway de API, e ele calcula o URI dinâmico a partir do cabeçalho de autorização e encaminha a solicitação para o URI (veja o diagrama abaixo para entender o fluxo). Para fazer isso, vamos implementar um roteamento dinâmico no nível do Gateway de API Apache APISIX com base na reivindicação do token JWT por meio do uso dos seguintes plugins:

  1. Plugin openid-connect que interage com o provedor de identidade (IdP) e pode interceptar solicitações não autenticadas a tempo para aplicativos de backend. Como provedor de identidade, usamos o Okta que emite um token JWT com nossa reivindicação personalizada e valida o token JWT. Ou você pode usar outros IdPs como Keycloak, Ory Hydra, ou até mesmo usar o jwt-plugin para criar um token JWT, autenticar e autorizar solicitações.
  2. Plugin serverless-pre-function para escrever um código de função Lua personalizado que intercepta a solicitação, decodifica, analisa uma reivindicação de token JWT e armazena o valor da reivindicação em um novo cabeçalho personalizado para tomar decisões de autorização posteriores.
  3. Plugin proxy-rewrite, uma vez que temos a reivindicação no cabeçalho, usamos esse plugin como mecanismo de encaminhamento de solicitação para determinar qual caminho URI precisa ser usado para recuperar sessões específicas do palestrante com base na variável de cabeçalho Nginx, no nosso caso, é speakerId que muda dinamicamente para criar diferentes caminhos /speaker/$http_speakerId/sessions. O plugin encaminhará a solicitação para o recurso relacionado na Conference API.

Depois de entender o que vamos cobrir ao longo da demonstração, vamos verificar os pré-requisitos para começar a configurar o cenário acima e concluir o tutorial.

Pré-requisitos

Configurar o serviço de backend (upstream)

Você precisará configurar o serviço de backend para a Conference API para o qual deseja rotear as solicitações. Isso pode ser feito adicionando um servidor upstream no Apache APISIX por meio da API Admin.

curl "http://127.0.0.1:9180/apisix/admin/upstreams/1" -X PUT -d '
{
  "name": "Conferences API upstream",
  "desc": "Register Conferences API as the upstream",
  "type": "roundrobin",
  "scheme": "https",
  "nodes": {
    "conferenceapi.azurewebsites.net:443": 1
  }
}'

Criar uma Configuração de Plugin

Em seguida, configuramos um novo objeto de configuração de plugin. Usaremos 3 plugins: openid-connect, serverless-pre-function e proxy-rewrite, respectivamente, como discutimos os casos de uso de cada plugin anteriormente. Você precisa substituir apenas os atributos do plugin openid-connect (ClienID, Secret, Discovery e Introspection endpoints) pelos seus próprios detalhes do Okta antes de executar o comando curl.

curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PUT -d '
{
    "plugins": {
        "openid-connect":{
            "client_id":"{YOUR_OKTA_CLIENT_ID}",
            "client_secret":"{YOUR_OKTA_CLIENT_SECRET}",
            "discovery":"https://{YOUR_OKTA_ISSUER}/oauth2/default/.well-known/openid-configuration",
            "scope":"openid",
            "bearer_only":true,
            "realm":"master",
            "introspection_endpoint_auth_method":"https://{YOUR_OKTA_ISSUER}/oauth2/v1/introspect",
            "redirect_uri":"https://conferenceapi.azurewebsites.net/"
        },
        "proxy-rewrite": {
            "uri": "/speaker/$http_speakerId/sessions",
            "host":"conferenceapi.azurewebsites.net"
        },
        "serverless-pre-function": {
            "phase": "rewrite",
            "functions" : ["return function(conf, ctx)

    -- Importar bibliotecas necessárias
    local core = require(\"apisix.core\")
    local jwt = require(\"resty.jwt\")

    -- Recuperar o token JWT do cabeçalho Authorization
    local jwt_token = core.request.header(ctx, \"Authorization\")
    if jwt_token ~= nil then
        -- Remover o prefixo Bearer do token JWT
        local _, _, jwt_token_only = string.find(jwt_token, \"Bearer%s+(.+)\")
        if jwt_token_only ~= nil then
           -- Decodificar o token JWT
           local jwt_obj = jwt:load_jwt(jwt_token_only)

           if jwt_obj.valid then
             -- Recuperar o valor da reivindicação speakerId do token JWT
             local speakerId_claim_value = jwt_obj.payload.speakerId

             -- Armazenar o valor da reivindicação speakerId na variável de cabeçalho
             core.request.set_header(ctx, \"speakerId\", speakerId_claim_value)
           end
         end
     end
   end
    "]}
    }
}'

Na configuração acima, a parte mais difícil de entender pode ser o código de função personalizado que escrevemos em Lua dentro do plugin serverless-pre-function:

return function(conf, ctx)
    -- Importar bibliotecas necessárias
    local core = require(\"apisix.core\")
    local jwt = require(\"resty.jwt\")

    -- Recuperar o token JWT do cabeçalho Authorization
    local jwt_token = core.request.header(ctx, \"Authorization\")
    if jwt_token ~= nil then
        -- Remover o prefixo Bearer do token JWT
        local _, _, jwt_token_only = string.find(jwt_token, \"Bearer%s+(.+)\")
        if jwt_token_only ~= nil then
           -- Decodificar o token JWT
           local jwt_obj = jwt:load_jwt(jwt_token_only)

           if jwt_obj.valid then
             -- Recuperar o valor da reivindicação speakerId do token JWT
             local speakerId_claim_value = jwt_obj.payload.speakerId

             -- Armazenar o valor da reivindicação speakerId na variável de cabeçalho
             core.request.set_header(ctx, \"speakerId\", speakerId_claim_value)
           end
         end
   end
end

Basicamente, esse plugin será executado antes dos outros dois plugins e faz o seguinte:

  1. Recupera o token JWT do cabeçalho Authorization.
  2. Remove o prefixo "Bearer " do token JWT.
  3. Decodifica o token JWT usando a biblioteca resty.jwt.
  4. Recupera o valor da reivindicação "speakerId" do token JWT decodificado.
  5. Finalmente, armazena o valor da reivindicação "speakerId" na variável de cabeçalho speakerId.

Configurar uma nova Rota

Esta etapa envolve a configuração de uma nova rota que usa a configuração do plugin e a configuração da rota para funcionar com o upstream (referenciando seus IDs) que criamos nas etapas anteriores:

curl "http://127.0.0.1:9180/apisix/admin/routes/1"  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "name":"Conferences API speaker sessions route",
    "desc":"Create a new route in APISIX for the Conferences API speaker sessions",
    "methods": ["GET"],
    "uri": "/sessions",
    "upstream_id":"1",
    "plugin_config_id":1
}'

Na configuração acima, definimos as regras de correspondência de rota, como apenas solicitações HTTP GET para o URI /sessions serão roteadas para o serviço de backend correto.

Obter um token do Okta

Depois de configurar o upstream, os plugins e a rota no lado do APISIX, agora solicitamos um token do Okta que contém nossa reivindicação personalizada speakerId. Você pode seguir o guia que inclui informações sobre como construir uma URL para solicitar um token com o Okta ou simplesmente usar a URL resultante abaixo com seu emissor e ID de cliente do Okta:

https://{YOUR_OKTA_ISSUER}/oauth2/default/v1/authorize?client_id={YOUR_OKTA_CLIENT_ID}
&response_type=id_token
&scope=openid
&redirect_uri=https%3A%2F%2Fconferenceapi.azurewebsites.net
&state=myState
&nonce=myNonceValue

Depois de colar a solicitação no seu navegador, o navegador será redirecionado para a página de login do Okta e gerará um Token de ID.

https://conferenceapi.azurewebsites.net/#id_token={TOKEN_WILL_BE_HERE}

Observe que o processo para recuperar um token pode ser diferente para outros provedores de identidade.

Para verificar o token de ID retornado, você pode copiar o valor e colá-lo em qualquer decodificador JWT (por exemplo, https://token.dev).

Testar o roteamento dinâmico

Finalmente, agora podemos verificar se a solicitação está sendo roteada para o caminho URI correto (com sessões específicas do palestrante) com base nos critérios de correspondência e na reivindicação do token JWT executando outro comando curl simples:

curl -i -X "GET [http://127.0.0.1:9080/sessions](http://127.0.0.1:9080/sessions)" -H "Authorization: Bearer {YOUR_OKTA_JWT_TOKEN}"

Aqui está, o resultado como esperávamos. Se definirmos o speakerId como 1 na reivindicação JWT do Okta, o Apisix roteou a solicitação para o caminho URI relevante e retornou todas as sessões desse palestrante na resposta.

{
  "collection": {
    "version": "1.0",
    "links": [],
    "items": [
      {
        "href": "https://conferenceapi.azurewebsites.net/session/114",
        "data": [
          {
            "name": "Title",
            "value": "\r\n\t\t\tIntroduction to Windows Azure Part I\r\n\t\t"
          },
          {
            "name": "Timeslot",
            "value": "04 December 2013 13:40 - 14:40"
          },
          {
            "name": "Speaker",
            "value": "Scott Guthrie"
          }
        ],
        "links": [
          {
            "rel": "http://tavis.net/rels/speaker",
            "href": "https://conferenceapi.azurewebsites.net/speaker/1"
          },
          {
            "rel": "http://tavis.net/rels/topics",
            "href": "https://conferenceapi.azurewebsites.net/session/114/topics"
          }
        ]
      },
      {
        "href": "https://conferenceapi.azurewebsites.net/session/121",
        "data": [
          {
            "name": "Title",
            "value": "\r\n\t\t\tIntroduction to Windows Azure Part II\r\n\t\t"
          },
          {
            "name": "Timeslot",
            "value": "04 December 2013 15:00 - 16:00"
          },
          {
            "name": "Speaker",
            "value": "Scott Guthrie"
          }
        ],
      }
    ]
   }
}

Conclusões

  • Com o Gateway de API, você pode rotear o tráfego para diferentes serviços de backend com base em vários critérios.
  • O roteamento dinâmico pode ser alcançado dependendo dos atributos do usuário especificados no cabeçalho, consulta ou corpo da solicitação.
  • Você pode criar regras de roteamento complexas que levam em consideração as reivindicações presentes no token JWT e garantir que apenas solicitações autorizadas tenham permissão para acessar sua API.
Tags: