Pular para conteúdo

openidc: Implementação do OpenID Connect Relying Party e OAuth 2.0 Resource Server em Lua para NGINX / nginx-module-lua

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Então você pode prosseguir com os seguintes passos.

CentOS/RHEL 7 ou Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-openidc

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-openidc

Para usar esta biblioteca Lua com o NGINX, certifique-se de que o nginx-module-lua está instalado.

Este documento descreve lua-resty-openidc v1.8.0 lançado em 13 de setembro de 2024.


CI Status Certificação OpenID

lua-resty-openidc

lua-resty-openidc é uma biblioteca para NGINX que implementa a funcionalidade de Relying Party (RP) do OpenID Connect e/ou do Resource Server (RS) do OAuth 2.0.

Quando usada como um Relying Party do OpenID Connect, autentica usuários contra um Provedor de OpenID Connect usando OpenID Connect Discovery e o Basic Client Profile (ou seja, o fluxo de Código de Autorização). Quando usada como um Resource Server do OAuth 2.0, pode validar Tokens de Acesso Bearer do OAuth 2.0 contra um Servidor de Autorização ou, caso um JSON Web Token seja usado como Token de Acesso, a verificação pode ocorrer contra um segredo/chave pré-configurado.

Ela mantém sessões para usuários autenticados aproveitando lua-resty-session, oferecendo assim uma escolha configurável entre armazenar o estado da sessão em um cookie do navegador do lado do cliente ou usar um dos mecanismos de armazenamento do lado do servidor shared-memory|memcache|redis.

Ela suporta cache em todo o servidor de documentos de Discovery resolvidos e Tokens de Acesso validados.

Pode ser usada como um proxy reverso terminando OAuth/OpenID Connect na frente de um servidor de origem, de modo que o servidor/serviços de origem possam ser protegidos com os padrões relevantes sem implementar esses no próprio servidor.

Configuração de Exemplo para Login com Google+

Configuração de exemplo nginx.conf para autenticar usuários contra o Login do Google+, protegendo um caminho proxy reverso.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
  lua_ssl_verify_depth 5;

  # cache para documentos de metadados de discovery
  lua_shared_dict discovery 1m;
  # cache para JWKs
  lua_shared_dict jwks 1m;

  # NB: se você tiver "lua_code_cache off;", use:
  # set $session_secret xxxxxxxxxxxxxxxxxxx;
  # veja: https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- o URI de redirecionamento completo deve ser protegido por este script
             -- se o URI começar com uma / o URI de redirecionamento completo se torna
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- a menos que o esquema tenha sido substituído usando opts.redirect_uri_scheme ou um cabeçalho X-Forwarded-Proto na solicitação de entrada
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- até a versão 1.6.1 você especificaria
             -- redirect_uri_path = "/redirect_uri",
             -- e não poderia definir o nome do host

             -- O endpoint de discovery do OP. Ative para obter o URI de todos os endpoints (Token, introspecção, logout...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- O acesso ao endpoint Token do OP requer uma autenticação. Vários modos de autenticação são suportados:
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o Se token_endpoint_auth_method estiver definido como "client_secret_basic", "client_secret_post" ou "client_secret_jwt", a autenticação para o endpoint Token é feita usando client_id e client_secret
             --   Para OPs não conformes ao RFC 6749 do OAuth 2.0 para Autenticação do cliente (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id e client_secret DEVEM ser invariantes quando codificados na URL
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o Se token_endpoint_auth_method estiver definido como "private_key_jwt", a autenticação para o endpoint Token é feita usando client_id, client_rsa_private_key e client_rsa_private_key_id para calcular um JWT assinado
             --   client_rsa_private_key é a chave privada RSA a ser usada para assinar o JWT gerado pelo lua-resty-openidc para autenticação ao OP
             --   client_rsa_private_key_id (opcional) é o id da chave a ser definido no cabeçalho do JWT para identificar qual chave pública o OP deve usar para verificar a assinatura do JWT
             --client_id = "<client_id>",
             --client_rsa_private_key=[[-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAiThmpvXBYdur716D2q7fYKirKxzZIU5QrkBGDvUOwg5izcTv
[...]
h2JHukolz9xf6qN61QMLSd83+kwoBr2drp6xg3eGDLIkQCQLrkY=
-----END RSA PRIVATE KEY-----]],
             --client_rsa_private_key_id="key id#1",
             --   Duração de vida expressa em segundos do JWT assinado gerado pelo lua-resty-openidc para autenticação ao OP.
             --   (usado quando token_endpoint_auth_method está definido como "private_key_jwt" ou "client_secret_jwt"). O padrão é 60 segundos.
             --client_jwt_assertion_expires_in = 60,
             -- Ao usar https para qualquer endpoint do OP, a verificação do certificado SSL pode ser exigida ("yes") ou não ("no").
             --ssl_verify = "no",
             -- A conexão keepalive com o OP pode ser habilitada ("yes") ou desabilitada ("no").
             --keepalive = "no",

             --response_mode=form_post pode ser usado para fazer lua-resty-openidc usar o [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Nota* para navegadores modernos você precisará definir [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) como `None` com form_post, a menos que seu Provedor de OpenID Connect e Relying Party compartilhem o mesmo domínio.
             --authorization_params = { hd="zmartzone.eu" },
             --scope = "openid email profile",
             -- Atualize o id_token do usuário após 900 segundos sem exigir reautenticação
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             -- Para onde o usuário deve ser redirecionado após o logout do RP. Esta opção substitui qualquer end_session_endpoint que o OP possa ter fornecido na resposta de discovery.
             --redirect_after_logout_with_id_token_hint = true,
             -- Se o redirecionamento após o logout deve incluir o id token como uma dica (se disponível). Esta opção é usada apenas se redirect_after_logout_uri estiver definido.
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- Para onde o RP solicita que o OP redirecione o usuário após o logout. Se esta opção estiver definida como um URI relativo, será relativo ao endpoint de logout do OP, não ao do RP.

             --accept_none_alg = false
             -- se seu Provedor de OpenID Connect não assina seus id tokens
             -- (usa o algoritmo de assinatura "none"), então defina isso como verdadeiro.

             --accept_unsupported_alg = true
             -- se você quiser rejeitar tokens assinados usando um algoritmo
             -- não suportado pelo lua-resty-jwt, defina isso como falso. Se
             -- você deixar não definido ou definir como verdadeiro, a assinatura do token não será
             -- verificada quando um algoritmo não suportado for usado.

             --renew_access_token_on_expiry = true
             -- se este plugin deve tentar renovar silenciosamente o token de acesso uma vez que ele expire, se um token de atualização estiver disponível.
             -- se falhar ao renovar o token, o usuário será redirecionado para o endpoint de autorização.
             --access_token_expires_in = 3600
             -- Tempo de vida padrão em segundos do access_token se nenhum atributo expires_in estiver presente na resposta do endpoint de token.

             --access_token_expires_leeway = 0
             --  Margem de expiração para renovação do access_token. Se isso estiver definido, a renovação acontecerá access_token_expires_leeway segundos antes da expiração do token. Isso evita erros caso o access_token expire ao chegar ao OAuth Resource Server.

             --force_reauthorize = false
             -- Quando force_reauthorize está definido como verdadeiro, o fluxo de autorização será executado mesmo que um token  tenha sido armazenado em cache
             --session_contents = {id_token=true}
             -- Lista de permissões do conteúdo da sessão a ser habilitada. Isso pode ser usado para reduzir o tamanho da sessão.
             -- Quando não definido, tudo será incluído na sessão.
             -- Disponíveis são:
             -- id_token, enc_id_token, user, access_token (inclui token de atualização)

             -- Você pode especificar timeouts para connect/send/read como um único número (definindo todos os timeouts) ou como uma tabela. Os valores estão em milissegundos
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- Por padrão, a solicitação de autorização inclui o
             -- parâmetro nonce. Você pode usar esta opção para desativá-lo
             -- o que pode ser necessário ao falar com um Provedor de OpenID
             -- Connect quebrado que ignora o parâmetro, pois o
             -- id_token será rejeitado caso contrário.

             --revoke_tokens_on_logout = false
             -- Quando revoke_tokens_on_logout está definido como verdadeiro, um logout notifica o servidor de autorização que os tokens de atualização e acesso obtidos anteriormente não são mais necessários. Isso requer que o revocation_endpoint seja descobrível.
             -- Se não houver um endpoint de revogação fornecido ou se houver erros na revogação, o usuário não será notificado e o processo de logout continuará normalmente.

             -- Opcional: use proxy de saída para os endpoints do Provedor de OpenID Connect com a tabela proxy_opts:
             -- isso requer lua-resty-http >= 0.12
             -- proxy_opts = {
             --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
             --    https_proxy = "http://<proxy_host>:<proxy_port>/"
             -- }

             -- Hooks de Ciclo de Vida
             --
             -- lifecycle = {
             --    on_created = handle_created,
             --    on_authenticated = handle_authenticated,
             --    on_regenerated = handle_regenerated
             --    on_logout = handle_logout
             -- }
             --
             -- onde `handle_created`, `handle_authenticated`, `handle_regenerated` e `handle_logout` são chamáveis
             -- aceitando o argumento `session`. `handle_created` aceita também um segundo argumento `params` que é uma tabela
             -- contendo os parâmetros de consulta da solicitação de autorização usada para redirecionar o usuário para o endpoint do Provedor de OpenID
             -- Connect.
             --
             --  -- O hook `on_created` é invocado *após* uma sessão ter sido criada em
             --     `openidc_authorize` imediatamente antes de salvar a sessão
             --  -- O hook `on_authenticated` é invocado *após* receber a resposta de autorização em
             --     `openidc_authorization_response` imediatamente antes de salvar a sessão
             --     A partir da versão 1.7.5 do lua-resty-openidc, isso recebe o id_token decodificado como segundo e a resposta do endpoint de token como terceiro argumento      
             --  -- O `on_regenerated` é invocado imediatamente após o
                     um novo token de acesso ter sido obtido via refresh de token
                     e é chamado com a tabela de sessão regenerada
             --  -- O hook `on_logout` é invocado *antes* que uma sessão seja destruída em
             --     `openidc_logout`
             --
             --  Qualquer, todos ou nenhum dos hooks podem ser usados. Um `lifecycle` vazio não faz nada.
             --  Um hook que retorna um valor verdadeiro faz com que a ação do ciclo de vida da qual fazem parte falhe.

             -- Opcional: adicione um decorador para a solicitação HTTP que é
             -- aplicado quando lua-resty-openidc fala diretamente com o Provedor de OpenID Connect.
             -- Pode ser usado para fornecer cabeçalhos HTTP extras
             -- ou adicionar outro comportamento semelhante.
             -- http_request_decorator = function(req)
             --   local h = req.headers or {}
             --   h[EXTRA_HEADER] = 'meu cabeçalho extra'
             --   req.headers = h
             --   return req
             -- end,

             -- use_pkce = false,
             -- quando definido como verdadeiro, a "Proof Key for Code Exchange" conforme
             -- definido no RFC 7636 será usada. O método de desafio de código
             -- será sempre S256

          }

          -- chame authenticate para autenticação de usuário OpenID Connect
          local res, err = require("resty.openidc").authenticate(opts)

          if err then
            ngx.status = 500
            ngx.say(err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
          end

          -- neste ponto, res é uma tabela Lua com 3 chaves:
          --   id_token    : uma tabela Lua com as claims do id_token (obrigatório)
          --   access_token: o token de acesso (opcional)
          --   user        : uma tabela Lua com as claims retornadas do endpoint de informações do usuário (opcional)

          --if res.id_token.hd ~= "zmartzone.eu" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.user.email ~= "[email protected]" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          -- defina cabeçalhos com informações do usuário: isso irá sobrescrever quaisquer cabeçalhos existentes
          -- mas também limpá-los(!) caso nenhum valor seja fornecido no token
          ngx.req.set_header("X-USER", res.id_token.sub)
      }

      proxy_pass http://localhost:80;
    }
  }
}

Sobre redirect_uri

O chamado redirect_uri é um URI que faz parte do protocolo OpenID Connect. O URI de redirecionamento é registrado com seu provedor de OpenID Connect e é o URI para o qual seu provedor redirecionará os usuários após o login bem-sucedido. Este URI é então tratado pelo lua-resty-openidc, onde obtém tokens e realiza algumas verificações e somente após isso o navegador é redirecionado para onde seu usuário queria ir inicialmente.

O redirect_uri não deve ser tratado pelo seu código de aplicação. Ele deve ser um URI do qual o lua-resty-openidc é responsável, portanto, deve estar em uma location protegida pelo lua-resty-openidc.

Você configura o redirect_uri do lado do lua-resty-openidc através do parâmetro opts.redirect_uri (que por padrão é /redirect_uri). Se começar com uma /, então o lua-resty-openidc irá adicionar o protocolo e o nome do host atual a ele ao enviar o URI para o provedor de OpenID Connect (levando em conta os cabeçalhos HTTP Forwarded e X-Forwarded-*). Mas você também pode especificar um URI absoluto contendo host e protocolo você mesmo.

Antes da versão 1.6.1, opts.redirect_uri_path era a maneira de configurar o redirect_uri sem qualquer opção para controlar as partes de protocolo e host.

Sempre que o lua-resty-openidc "vê" um caminho local navegável que corresponde ao caminho de opts.redirect_uri (ou opts.redirect_uri_path), ele interceptará a solicitação e a tratará.

Isso funciona para a maioria dos casos, mas às vezes o redirect_uri visível externamente tem um caminho diferente do que o visível localmente para o servidor. Isso pode acontecer se um proxy reverso na frente do seu servidor reescrever URIs antes de encaminhar as solicitações. Portanto, a versão 1.7.6 introduziu uma nova opção opts.local_redirect_uri_path. Se estiver definida, o lua-resty-openidc interceptará solicitações para esse caminho em vez do caminho de opts.redirect_uri.

Verificar autenticação apenas

-- verifica a sessão, mas não redireciona para autenticação se não estiver logado
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

Verificar autenticação apenas e negar acesso não autenticado

-- verifica a sessão, não redireciona para autenticação se não estiver logado, mas retorna um erro em vez disso
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

Sessões e Bloqueio

A função authenticate retorna o objeto de sessão atual como seu quarto argumento de retorno. Se você configurou o lua-resty-session para usar um backend de armazenamento do lado do servidor que utiliza bloqueio, a sessão pode ainda estar bloqueada quando for retornada. Nesse caso, você pode querer fechá-la explicitamente.

local res, err, target, session = require("resty.openidc").authenticate(opts)
session:close()

Cache

O lua-resty-openidc pode usar caches de memória compartilhada para várias coisas. Se você quiser que ele use os caches, deve usar lua_shared_dict no seu arquivo nginx.conf.

Atualmente, até quatro caches são usados:

  • o cache chamado discovery armazena os metadados de Discovery do OpenID Connect do seu Provedor de OpenID Connect. Os itens do cache expiram após 24 horas, a menos que sejam substituídos por opts.discovery_expires_in (um valor dado em segundos). Este cache armazenará um item por URI de emissor e você pode consultar o documento de discovery você mesmo para obter uma estimativa do tamanho necessário - geralmente alguns kB por Provedor de OpenID Connect.
  • o cache chamado jwks armazena o material de chave do seu Provedor de OpenID Connect, se fornecido via o endpoint JWKS. Os itens do cache expiram após 24 horas, a menos que sejam substituídos por opts.jwks_expires_in. Este cache armazenará um item por URI de JWKS e você pode consultar o jwks você mesmo para obter uma estimativa do tamanho necessário - geralmente alguns kB por Provedor de OpenID Connect.
  • o cache chamado introspection armazena o resultado da introspecção do token OAuth2. Os itens do cache expiram quando o token correspondente expira. Tokens com expiração desconhecida não são armazenados em cache. Este cache conterá uma entrada por token de acesso introspectado - geralmente isso será alguns kB por token.
  • o cache chamado jwt_verification armazena o resultado da verificação de JWT. Os itens do cache expiram quando o token correspondente expira. Tokens com expiração desconhecida não são armazenados em cache por dois minutos. Este cache conterá uma entrada por JWT verificado - geralmente isso será alguns kB por token.

Cache de Resultados de Introspecção e Verificação de JWT

Observe que os caches jwt_verification e introspection são compartilhados entre todas as localizações configuradas. Se você estiver usando localizações com configurações opts diferentes, o cache compartilhado pode permitir que um token que é válido apenas para uma localização seja aceito por outra se for lido do cache. Para evitar confusão de cache, é recomendável definir opts.cache_segment como strings exclusivas para cada conjunto de localizações relacionadas.

Revogar tokens

A função revoke_tokens(opts, session) revoga o token de atualização e o token de acesso atuais. Em contraste com um logout completo, o cookie da sessão não será destruído e o endpoint de finalização de sessão não será chamado. A função retorna true se ambos os tokens foram revogados com sucesso. Esta função pode ser útil em cenários onde você deseja destruir/remover uma sessão do lado do servidor.

Com revoke_token(opts, token_type_hint, token) também é possível revogar um token específico. token_type_hint pode geralmente ser refresh_token ou access_token.

Configuração de Exemplo para Validação de Token JWT do OAuth 2.0

Configuração de exemplo nginx.conf para verificar Tokens de Acesso Bearer JWT contra um segredo/chave pré-configurado. Uma vez verificado com sucesso, o servidor NGINX pode funcionar como um proxy reverso para um servidor de origem interno.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  # cache para resultados de verificação de JWT
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. exemplo de um segredo compartilhado para verificação de assinatura HS???
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- nas versões até 1.6.1, a chave desta opção seria secret
            -- em vez de symmetric_key

            -- 2. outro exemplo de um certificado público para verificação de assinatura RS???
            public_key = [[-----BEGIN CERTIFICATE-----
MIIC0DCCAbigAwIBAgIGAVSbMZs1MA0GCSqGSIb3DQEBCwUAMCkxCzAJBgNVBAYTAlVTMQwwCgYD
VQQKEwNibGExDDAKBgNVBAMTA2JsYTAeFw0xNjA1MTAxNTAzMjBaFw0yNjA1MDgxNTAzMjBaMCkx
CzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNibGExDDAKBgNVBAMTA2JsYTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAIcLtHjX2GFxYv1033dvfohyCU6nsuR1qoDXfHTG3Mf/Yj4BfLHtMjJr
nR3sgHItH3B6qZPnfErfsN0LP4uZ10/74CrWVqT5dy6ecXMqYtz/KNJ8rG0vY8vltc417AU4fie8
gyeWv/Z6wHWUCf3NHRV8GfFgfuvywgUpHo8ujpUPFr+zrPr8butrzJPq1h3+r0f5P45tfWOdpjCT
gsTzK6urUG0k3WkwdDYapL3wRCAw597nYfgKzzXuh9N0ZL3Uj+eJ6BgCzUZDLXABpMBZfk6hmmzp
cAFV4nTf1AaAs/EOwVE0YgZBJiBrueMcteAIxKrKjEHgThU2Zs9gN9cSFicCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAQLU1A58TrSwrEccCIy0wxiGdCwQbaNMohzirc41zRMCXleJXbtsn1vv85J6A
RmejeH5f/JbDqRRRArGMdLooGbqjWG/lwZT456Q6DXqF2plkBvh37kp/GjthGyR8ODJn5ekZwxuB
OcTuruRhqYOIJjiYZSgK/P0zUw1cjLwUJ9ig/O6ozYmof83974fygA/wK3SgFNEoFlTkTpOvZhVW
9kLfCVA/CRBfJNKnz5PWBBxd/3XSEuP/fcWqKGTy7zZso4MTB0NKgWO4duGTgMyZbM4onJPyA0CY
lAc5Csj0o5Q+oEhPUAVBIF07m4rd0OvAVPOCQ2NJhQSL1oWASbf+fg==
-----END CERTIFICATE-----]],
            -- nas versões até 1.6.1, a chave desta opção seria secret
            -- em vez de public_key

            -- 3. alternativamente, pode-se apontar para um chamado documento de Discovery que
            -- contém a entrada "jwks_uri"; o endpoint jwks deve fornecer ou uma entrada "x5c"
            -- ou tanto as entradas "n" módulo e "e" expoente para verificação de assinatura RSA
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- o algoritmo de assinatura que você espera que tenha sido usado;
             -- pode ser uma única string ou uma tabela.
             -- Você deve definir isso por razões de segurança para
             -- evitar aceitar um token que afirma ser assinado por HMAC
             -- usando uma chave pública RSA.
             --token_signing_alg_values_expected = { "RS256" }

             -- se você quiser aceitar tokens não assinados (usando o
             -- algoritmo de assinatura "none"), então defina isso como verdadeiro.
             --accept_none_alg = false

             -- se você quiser rejeitar tokens assinados usando um algoritmo
             -- não suportado pelo lua-resty-jwt, defina isso como falso. Se
             -- você deixar não definido, a assinatura do token não será
             -- verificada de forma alguma.
             --accept_unsupported_alg = true

             -- o tempo de expiração em segundos para o cache jwk, o padrão é 1 dia.
             --jwk_expires_in = 24 * 60 * 60

             -- Pode ser necessário forçar a verificação para um token bearer e ignorar os resultados de verificação em cache existentes. Se sim, você precisa definir a opção jwt_verification_cache_ignore como verdadeira.
             -- jwt_verification_cache_ignore = true

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de maneira diferente
             -- cache_segment = 'api'
          }

          -- chame bearer_jwt_verify para validação de JWT do OAuth 2.0
          local res, err = require("resty.openidc").bearer_jwt_verify(opts)

           if err or not res then
            ngx.status = 403
            ngx.say(err and err or "nenhum access_token fornecido")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- neste ponto, res é uma tabela Lua que representa o (validado) JSON
          -- payload no token JWT; agora geralmente não queremos permitir apenas qualquer
          -- token que foi emitido pelo Servidor de Autorização, mas queremos aplicar
          -- algumas restrições de acesso via IDs de cliente ou escopos

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';

       proxy_pass http://localhost:80;
    }
  }
}

Configuração de Exemplo para PingFederate OAuth 2.0

Configuração de exemplo nginx.conf para validar Tokens de Acesso Bearer contra um Servidor de Autorização PingFederate OAuth 2.0.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # cache para resultados de validação
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             introspection_endpoint="https://localhost:9031/as/introspect.oauth2",
             client_id="rs_client",
             client_secret="2Federate",
             ssl_verify = "no",

             -- Padrão para "exp" - Controla o TTL do cache de introspecção
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de maneira diferente
             -- cache_segment = 'api'
          }

          -- chame introspect para validação do Token de Acesso Bearer do OAuth 2.0
          local res, err = require("resty.openidc").introspect(opts)

          if err then
            ngx.status = 403
            ngx.say(err)
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- neste ponto, res é uma tabela Lua que representa o objeto JSON
          -- retornado do endpoint de introspecção/validação

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Configuração de exemplo nginx.conf para validar Tokens de Acesso Bearer passados como cookie contra um Servidor de Autorização ORY/Hydra.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # cache para resultados de validação
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- define o URI do endpoint de introspecção
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- alternativamente, se seu Provedor OAuth2 fornecer um documento de discovery que contém a
             -- claim introspection_endpoint, você pode deixar a opção introspection_endpoint
             -- não definida e em vez disso usar
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

             client_id="admin",
             client_secret="demo-password",
             ssl_verify = "no",

             -- Define o intervalo em segundos após o qual um token de acesso introspectado e em cache precisa
             -- ser atualizado ao introspectá-lo (e validá-lo) novamente contra o Servidor de Autorização.
             -- Quando não definido, o valor é 0, o que significa que ele  expira após o `exp` (ou alternativa,
             -- veja introspection_expiry_claim) dica conforme retornada pelo Servidor de Autorização
             -- introspection_interval = 60,

             -- Define a forma como os tokens de acesso OAuth 2.0 Bearer podem ser passados para este Resource Server.
             -- "cookie" como um cabeçalho de cookie chamado "PA.global" ou usando o nome especificado após ":"
             -- "header" cabeçalho "Authorization: bearer"
             -- Quando não definido, o cabeçalho padrão "Authorization: bearer" é usado
             -- auth_accept_token_as = "cookie:PA",

             -- Se o cabeçalho for usado, o campo do cabeçalho é Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- Método de autenticação para o endpoint de introspecção do Servidor de Autorização OAuth 2.0,
             -- Usado para autenticar o cliente no endpoint de introspecção com um client_id/client_secret
             -- Padrão para "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- Especifique os nomes dos cookies separados por espaço para pegar do navegador e enviar junto em chamadas de backchannel
             -- para os endpoints OP e AS.
             -- Quando não definido, nenhum desses cookies é enviado.
             -- pass_cookies = "JSESSION"

             -- Padrão para "exp" - Controla o TTL do cache de introspecção
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- Pode ser necessário forçar uma chamada de introspecção para um access_token e ignorar os resultados de introspecção em cache existentes. Se sim, você precisa definir a opção introspection_cache_ignore como verdadeira.
             -- introspection_cache_ignore = true

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de maneira diferente
             -- cache_segment = 'api'
          }

          -- chame introspect para validação do Token de Acesso Bearer do OAuth 2.0
          local res, err = require("resty.openidc").introspect(opts)

          if err then
            ngx.status = 403
            ngx.say(err)
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- neste ponto, res é uma tabela Lua que representa o objeto JSON
          -- retornado do endpoint de introspecção/validação

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Registro

O registro pode ser personalizado, incluindo o uso de um logger personalizado e remapeando os níveis de log padrão do OpenIDC, por exemplo:

local openidc = require("resty.openidc")
openidc.set_logging(nil, { DEBUG = ngx.INFO })

Executando Testes

Criamos uma configuração dockerizada para o teste a fim de simplificar a instalação de dependências.

Para executar os testes, execute

$ docker build -f tests/Dockerfile . -t lua-resty-openidc/test
$ docker run -it --rm lua-resty-openidc/test:latest

se você quiser criar luacov cobertura enquanto testa, use

$ docker run -it --rm -e coverage=t lua-resty-openidc/test:latest

como o segundo comando

Suporte

Para perguntas genéricas, veja as páginas do Wiki com Perguntas Frequentes em:
https://github.com/zmartzone/lua-resty-openidc/wiki
Qualquer dúvida/problema deve ser direcionada para as Discussões ou rastreador de Problemas do Github.

Isenção de Responsabilidade

Este software é de código aberto pela ZmartZone IAM, mas não é suportado comercialmente como tal. Qualquer dúvida/problema deve ser direcionada para as Discussões ou rastreador de Problemas do Github. Veja também o arquivo DISCLAIMER neste diretório.

GitHub

Você pode encontrar dicas de configuração adicionais e documentação para este módulo no repositório do GitHub para nginx-module-openidc.