lua-resty-openidc

一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和 OAuth 2.0 资源服务器 (RS) 功能

$ opm get burnes/lua-resty-openidc

[!CI 状态](https://github.com/zmartzone/lua-resty-openidc/actions/workflows/docker-ci.yml) "<img width="184" height="96" align="right" src="http://openid.net/wordpress-content/uploads/2016/04/oid-l-certification-mark-l-rgb-150dpi-90mm@2x.png" alt="OpenID Certification""|https://openid.net/certification>

lua-resty-openidc

lua-resty-openidc 是一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和/或 OAuth 2.0 资源服务器 (RS) 功能。

当用作 OpenID Connect 依赖方时,它使用 OpenID Connect 发现 和基本客户端配置文件(即授权码流程)针对 OpenID Connect 提供商对用户进行身份验证。当用作 OAuth 2.0 资源服务器时,它可以针对授权服务器验证 OAuth 2.0 票据访问令牌,或者,如果访问令牌使用 JSON Web 令牌,则可以针对预配置的密钥/密钥进行验证。

它通过利用 lua-resty-session 为已认证的用户维护会话,从而提供在客户端浏览器 cookie 中存储会话状态或使用服务器端存储机制 shared-memory|memcache|redis 之间的可配置选择。

它支持已解析的发现文档和已验证的访问令牌的服务器范围缓存。

它可以用作反向代理,终止 OAuth/OpenID Connect 并位于源服务器之前,以便源服务器/服务可以使用相关标准进行保护,而无需在服务器本身上实现这些标准。

依赖项

lua-resty-openidc 依赖于以下软件包

以上依赖项会随 OpenResty 自动提供。您需要安装两个额外的纯 Lua 依赖项,它们分别实现会话管理和 HTTP 客户端功能

通常 - 当作为 OpenID Connect RP 或使用 JWT 访问令牌的 OAuth 2.0 服务器运行时 - 您还需要安装以下依赖项

当仅作为 OAuth 2.0 资源服务器运行并使用远程内省进行访问令牌验证时,上面提到的 lua-resty-jwt 依赖项不是必需的。

安装

使用 luarocks 执行以下操作

     luarocks install lua-resty-openidc

否则将 openidc.lua 复制到 lua_package_path 下名为 resty 的目录中的某个位置。如果您使用的是 OpenResty,则默认位置为 /usr/local/openresty/lualib/resty

旧版本的 lua-resty-openidc 也可以使用 opm 安装,但这已不再支持。

Google+ 登录的示例配置

针对 Google+ 登录对用户进行身份验证并保护反向代理路径的示例 nginx.conf 配置。

    events {
      worker_connections 128;
    }
    
    http {
    
      lua_package_path '~/lua/?.lua;;';
    
      resolver 8.8.8.8;
    
      lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
      lua_ssl_verify_depth 5;
    
      # cache for discovery metadata documents
      lua_shared_dict discovery 1m;
      # cache for JWKs
      lua_shared_dict jwks 1m;
    
      # NB: if you have "lua_code_cache off;", use:
      # set $session_secret xxxxxxxxxxxxxxxxxxx;
      # see: https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off
    
      server {
        listen 8080;
    
        location / {
    
          access_by_lua_block {
    
              local opts = {
                 -- the full redirect URI must be protected by this script
                 -- if the URI starts with a / the full redirect URI becomes
                 -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
                 -- unless the scheme was overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
                 redirect_uri = "https://MY_HOST_NAME/redirect_uri",
                 -- up until version 1.6.1 you'd specify
                 -- redirect_uri_path = "/redirect_uri",
                 -- and could not set the hostname
    
                 -- The discovery endpoint of the OP. Enable to get the URI of all endpoints (Token, introspection, logout...)
                 discovery = "https://#/.well-known/openid-configuration",
    
                 -- Access to OP Token endpoint requires an authentication. Several authentication modes are supported:
                 --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
                 -- o If token_endpoint_auth_method is set to "client_secret_basic", "client_secret_post", or "client_secret_jwt", authentication to Token endpoint is using client_id and client_secret
                 --   For non compliant OPs to OAuth 2.0 RFC 6749 for client Authentication (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
                 --   client_id and client_secret MUST be invariant when url encoded
                 client_id = "<client_id>",
                 client_secret = "<client_secret>",
                 -- o If token_endpoint_auth_method is set to "private_key_jwt" authentication to Token endpoint is using client_id, client_rsa_private_key and client_rsa_private_key_id to compute a signed JWT
                 --   client_rsa_private_key is the RSA private key to be used to sign the JWT generated by lua-resty-openidc for authentication to the OP
                 --   client_rsa_private_key_id (optional) is the key id to be set in the JWT header to identify which public key the OP shall use to verify the JWT signature
                 --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",
                 --   Life duration expressed in seconds of the signed JWT generated by lua-resty-openidc for authentication to the OP.
                 --   (used when token_endpoint_auth_method is set to "private_key_jwt" or "client_secret_jwt" authentication). Default is 60 seconds.
                 --client_jwt_assertion_expires_in = 60,
                 -- When using https to any OP endpoints, enforcement of SSL certificate check can be mandated ("yes") or not ("no").
                 --ssl_verify = "no",
                 -- Connection keepalive with the OP can be enabled ("yes") or disabled ("no").
                 --keepalive = "no",
    
                 --response_mode=form_post can be used to make lua-resty-openidc use the [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Note* for modern browsers you will need to set [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) to `None` with form_post unless your OpenID Connect Provider and Relying Party share the same domain.
                 --authorization_params = { hd="zmartzone.eu" },
                 --scope = "openid email profile",
                 -- Refresh the users id_token after 900 seconds without requiring re-authentication
                 --refresh_session_interval = 900,
                 --iat_slack = 600,
                 --redirect_uri_scheme = "https",
                 --logout_path = "/logout",
                 --registrations_endpoint = "https://my-oauth2-provider/realms/my-realm/protocol/openid-connect/registrations"
                 --   OAuth endpoint to be used when redirect to registration page is desired. Also requires 'registrations_path' to be set.
                 --registrations_path = "/registration",
                 --   Path on which a redirect to the registration page should be provided instead of the authorization page. Also requires 'registrations_endpoint' to be set.
                 --redirect_after_logout_uri = "/",
                 -- Where should the user be redirected after logout from the RP. This option overides any end_session_endpoint that the OP may have provided in the discovery response.
                 --redirect_after_logout_with_id_token_hint = true,
                 -- Whether the redirection after logout should include the id token as an hint (if available). This option is used only if redirect_after_logout_uri is set.
                 --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
                 -- Where does the RP requests that the OP redirects the user after logout. If this option is set to a relative URI, it will be relative to the OP's logout endpoint, not the RP's.
    
                 --accept_none_alg = false
                 -- if your OpenID Connect Provider doesn't sign its id tokens
                 -- (uses the "none" signature algorithm) then set this to true.
    
                 --accept_unsupported_alg = true
                 -- if you want to reject tokens signed using an algorithm
                 -- not supported by lua-resty-jwt set this to false. If
                 -- you leave it unset or set it to true, the token signature will not be
                 -- verified when an unsupported algorithm is used.
    
                 --renew_access_token_on_expiry = true
                 -- whether this plugin shall try to silently renew the access token once it is expired if a refresh token is available.
                 -- if it fails to renew the token, the user will be redirected to the authorization endpoint.
                 --access_token_expires_in = 3600
                 -- Default lifetime in seconds of the access_token if no expires_in attribute is present in the token endpoint response.
    
                 --access_token_expires_leeway = 0
                 --  Expiration leeway for access_token renewal. If this is set, renewal will happen access_token_expires_leeway seconds before the token expiration. This avoids errors in case the access_token just expires when arriving to the OAuth Resource Server.
    
                 --force_reauthorize = false
                 -- When force_reauthorize is set to true the authorization flow will be executed even if a token has been cached already
                 --session_contents = {id_token=true}
                 -- Whitelist of session content to enable. This can be used to reduce the session size.
                 -- When not set everything will be included in the session.
                 -- Available are:
                 -- id_token, enc_id_token, user, access_token (includes refresh token)
    
                 -- You can specify timeouts for connect/send/read as a single number (setting all timeouts) or as a table. Values are in milliseconds
                 -- timeout = 1000
                 -- timeout = { connect = 500, send = 1000, read = 1000 }
    
                 --use_nonce = false
                 -- By default the authorization request includes the
                 -- nonce paramter. You can use this option to disable it
                 -- which may be necessary when talking to a broken OpenID
                 -- Connect provider that ignores the paramter as the
                 -- id_token will be rejected otherwise.
    
                 --revoke_tokens_on_logout = false
                 -- When revoke_tokens_on_logout is set to true a logout notifies the authorization server that previously obtained refresh and access tokens are no longer needed. This requires that revocation_endpoint is discoverable.
                 -- If there is no revocation endpoint supplied or if there are errors on revocation the user will not be notified and the logout process continues normally.
    
                 -- Optional : use outgoing proxy to the OpenID Connect provider endpoints with the proxy_opts table :
                 -- this requires lua-resty-http >= 0.12
                 -- proxy_opts = {
                 --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
                 --    https_proxy = "http://<proxy_host>:<proxy_port>/"
                 -- }
    
                 -- Lifecycle Hooks
                 --
                 -- lifecycle = {
                 --    on_created = handle_created,
                 --    on_authenticated = handle_authenticated,
                 --    on_regenerated = handle_regenerated
                 --    on_logout = handle_logout
                 -- }
                 --
                 -- where `handle_created`, `handle_authenticated`, `handle_regenerated` and `handle_logout` are callables
                 -- accepting a single argument `session`
                 --
                 --  -- `on_created` hook is invoked *after* a session has been created in
                 --     `openidc_authorize` immediately prior to saving the session
                 --  -- `on_authenticated` hook is invoked *after* receiving authorization response in
                 --     `openidc_authorization_response` immediately prior to saving the session
                 --     Starting with lua-resty-openidc 1.7.5 this receives the decoded id_token as second and the response of the token endpoint as third argument      
                 --  -- `on_regenerated` is invoked immediately after the
                         a new access token has been obtained via token
                         refresh and is called with the regenerated session table
                 --  -- `on_logout` hook is invoked *before* a session is destroyed in
                 --     `openidc_logout`
                 --
                 --  Any, all or none of the hooks may be used. Empty `lifecycle` does nothing.
                 --  A hook that returns a truthy value causes the lifecycle action they are taking part of to fail.
    
                 -- Optional : add decorator for HTTP request that is
                 -- applied when lua-resty-openidc talks to the OpenID Connect
                 -- provider directly. Can be used to provide extra HTTP headers
                 -- or add other similar behavior.
                 -- http_request_decorator = function(req)
                 --   local h = req.headers or {}
                 --   h[EXTRA_HEADER] = 'my extra header'
                 --   req.headers = h
                 --   return req
                 -- end,
    
                 -- use_pkce = false,
                 -- when set to true the "Proof Key for Code Exchange" as
                 -- defined in RFC 7636 will be used. The code challenge
                 -- method will alwas be S256
    
              }
    
              -- call authenticate for OpenID Connect user authentication
              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
    
              -- at this point res is a Lua table with 3 keys:
              --   id_token    : a Lua table with the claims from the id_token (required)
              --   access_token: the access token (optional)
              --   user        : a Lua table with the claims returned from the user info endpoint (optional)
    
              --if res.id_token.hd ~= "zmartzone.eu" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
    
              --if res.user.email ~= "hans.zandbelt@zmartzone.eu" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
    
              -- set headers with user info: this will overwrite any existing headers
              -- but also scrub(!) them in case no value is provided in the token
              ngx.req.set_header("X-USER", res.id_token.sub)
          }
    
          proxy_pass http://localhost:80;
        }
      }
    }

关于 redirect_uri

所谓的 redirect_uri 是 OpenID Connect 协议的一部分。重定向 URI 在您的 OpenID Connect 提供商处注册,并且是您的提供商在成功登录后将用户重定向到的 URI。然后,此 URI 由 lua-resty-openidc 处理,它获取令牌并执行一些检查,只有在此之后,浏览器才会重定向到用户最初想要去的地方。

redirect_uri 不需要由您的应用程序代码处理。它必须是 lua-resty-openidc 负责的 URI,因此它必须位于由 lua-resty-openidc 保护的 location 中。

您可以通过 opts.redirect_uri 参数(默认为 /redirect_uri)在 lua-resty-openidc 侧配置 redirect_uri。如果它以 / 开头,则 lua-resty-openidc 会在将 URI 发送到 OpenID Connect 提供商时在其前面加上协议和当前主机名(考虑 ForwardedX-Forwarded-* HTTP 标头)。但是,您也可以自己指定包含主机和协议的绝对 URI。

在 1.6.1 版之前,opts.redirect_uri_path 是一种配置 redirect_uri 的方法,没有任何选项可以控制协议和主机部分。

每当 lua-resty-openidc “看到”导航到的本地路径与 opts.redirect_uri(或 opts.redirect_uri_path)的路径匹配时,它都会拦截请求并自行处理。

这适用于大多数情况,但有时外部可见的 redirect_uri 与服务器本地可见的路径不同。如果服务器前面的反向代理在转发请求之前重写了 URI,则可能会发生这种情况。因此,1.7.6 版引入了新选项 opts.local_redirect_uri_path。如果设置了该选项,则 lua-resty-opendic 将拦截对该路径的请求,而不是 opts.redirect_uri 的路径。

仅检查身份验证

    -- check session, but do not redirect to auth if not already logged in
    local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

仅检查身份验证并拒绝未经身份验证的访问

    -- check session, do not redirect to auth if not already logged in but return an error instead
    local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

会话和锁定

authenticate 函数将其第四个返回值作为当前会话对象返回。如果您已将 lua-resty-session 配置为使用使用锁定的服务器端存储后端,则在返回会话时它可能仍然处于锁定状态。在这种情况下,您可能希望显式关闭它

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

缓存

lua-resty-openidc 可以将 [共享内存缓存](https://github.com/openresty/lua-nginx-module/#lua_shared_dict) 用于多项操作。如果您希望它使用缓存,则必须在 nginx.conf 文件中使用 lua_shared_dict

目前最多使用四个缓存

  • 名为 discovery 的缓存存储 OpenID Connect 提供商的 OpenID Connect 发现元数据。缓存项在 24 小时后过期,除非由 opts.discovery_expires_in(以秒为单位的值)覆盖。此缓存将为每个颁发者 URI 存储一个项目,您可以自己查找发现文档以获取所需大小的估计值 - 通常每个 OpenID Connect 提供商为几 KB。

  • 名为 jwks 的缓存存储 OpenID Connect 提供商的关键材料(如果通过 JWKS 端点提供)。缓存项在 24 小时后过期,除非由 opts.jwks_expires_in 覆盖。此缓存将为每个 JWKS URI 存储一个项目,您可以自己查找 jwks 以获取所需大小的估计值 - 通常每个 OpenID Connect 提供商为几 KB。

  • 名为 introspection 的缓存存储 OAuth2 令牌内省的结果。缓存项在相应的令牌过期时过期。具有未知有效期的令牌根本不会被缓存。此缓存将包含每个内省的访问令牌的一个条目 - 通常每个令牌为几 KB。

  • 名为 jwt_verification 的缓存存储 JWT 验证的结果。缓存项在相应的令牌过期时过期。未知有效期的令牌不会缓存两分钟。此缓存将包含每个已验证 JWT 的一个条目 - 通常每个令牌为几 KB。

内省和 JWT 验证结果的缓存

请注意,jwt_verificationintrospection 缓存在所有配置的位置之间共享。如果您使用具有不同 opts 配置的位置,则共享缓存可能会允许仅对一个位置有效的令牌被另一个位置接受(如果从缓存中读取)。为了避免缓存混乱,建议为每组相关位置将 opts.cache_segment 设置为唯一的字符串。

吊销令牌

revoke_tokens(opts, session) 函数吊销当前的刷新令牌和访问令牌。与完全注销相反,会话 cookie 不会被销毁,也不会调用 endsession 端点。如果两个令牌都成功吊销,则该函数返回 true。此函数可能在您想要从服务器端销毁/删除会话的场景中很有用。

使用 revoke_token(opts, token_type_hint, token) 也可以吊销特定令牌。token_type_hint 通常可以是 refresh_tokenaccess_token

OAuth 2.0 JWT 令牌验证的示例配置

针对预配置的密钥/密钥验证票据 JWT 访问令牌的示例 nginx.conf 配置。成功验证后,NGINX 服务器可以充当内部源服务器的反向代理。

    events {
      worker_connections 128;
    }
    
    http {
    
      lua_package_path '~/lua/?.lua;;';
    
      resolver 8.8.8.8;
    
      # cache for JWT verification results
      lua_shared_dict jwt_verification 10m;
    
      server {
        listen 8080;
    
        location /api {
    
          access_by_lua '
    
              local opts = {
    
                -- 1. example of a shared secret for HS??? signature verification
                --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                -- in versions up to 1.6.1 this option's key would have been secret
                -- rather than symmetric_key
    
                -- 2. another example of a public cert for RS??? signature verification
                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-----]],
                -- in versions up to 1.6.1 this option's key would have been secret
                -- rather than public_key
    
                -- 3. alternatively one can point to a so-called Discovery document that
                -- contains "jwks_uri" entry; the jwks endpoint must provide either an "x5c" entry
                -- or both the "n" modulus and "e" exponent entries for RSA signature verification
                -- discovery = "https://#/.well-known/openid-configuration",
    
                 -- the signature algorithm that you expect has been used;
                 -- can be a single string or a table.
                 -- You should set this for security reasons in order to
                 -- avoid accepting a token claiming to be signed by HMAC
                 -- using a public RSA key.
                 --token_signing_alg_values_expected = { "RS256" }
    
                 -- if you want to accept unsigned tokens (using the
                 -- "none" signature algorithm) then set this to true.
                 --accept_none_alg = false
    
                 -- if you want to reject tokens signed using an algorithm
                 -- not supported by lua-resty-jwt set this to false. If
                 -- you leave it unset, the token signature will not be
                 -- verified at all.
                 --accept_unsupported_alg = true
    
                 -- the expiration time in seconds for jwk cache, default is 1 day.
                 --jwk_expires_in = 24 * 60 * 60
    
                 -- It may be necessary to force verification for a bearer token and ignore the existing cached
                 -- verification results. If so you need to set set the jwt_verification_cache_ignore option to true.
                 -- jwt_verification_cache_ignore = true
    
                 -- optional name of a cache-segment if you need separate
                 -- caches for differently configured locations
                 -- cache_segment = 'api'
              }
    
              -- call bearer_jwt_verify for OAuth 2.0 JWT validation
              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 "no access_token provided")
                ngx.exit(ngx.HTTP_FORBIDDEN)
              end
    
              -- at this point res is a Lua table that represents the (validated) JSON
              -- payload in the JWT token; now we typically do not want to allow just any
              -- token that was issued by the Authorization Server but we want to apply
              -- some access restrictions via client IDs or scopes
    
              --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;
        }
      }
    }

PingFederate OAuth 2.0 的示例配置

针对 PingFederate OAuth 2.0 授权服务器验证票据访问令牌的示例 nginx.conf 配置。

    events {
      worker_connections 128;
    }
    
    http {
    
      lua_package_path '~/lua/?.lua;;';
    
      resolver 8.8.8.8;
    
      lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
      lua_ssl_verify_depth 5;
    
      # cache for validation results
      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",
    
                 -- Defaults to "exp" - Controls the TTL of the introspection cache
                 -- https://tools.ietf.org/html/rfc7662#section-2.2
                 -- introspection_expiry_claim = "exp"
    
                 -- optional name of a cache-segment if you need separate
                 -- caches for differently configured locations
                 -- cache_segment = 'api'
              }
    
              -- call introspect for OAuth 2.0 Bearer Access Token validation
              local res, err = require("resty.openidc").introspect(opts)
    
              if err then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
              end
    
              -- at this point res is a Lua table that represents the JSON
              -- object returned from the introspection/validation endpoint
    
              --if res.scope ~= "edit" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
    
              --if res.client_id ~= "ro_client" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
          ';
        }
      }
    }

针对 ORY/Hydra 授权服务器验证作为 cookie 传递的票据访问令牌的示例 nginx.conf 配置。

    events {
      worker_connections 128;
    }
    
    http {
    
      lua_package_path '~/lua/?.lua;;';
    
      resolver 8.8.8.8;
    
      lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
      lua_ssl_verify_depth 5;
    
      # cache for validation results
      lua_shared_dict introspection 10m;
    
      server {
        listen 8080;
    
        location /api {
    
          access_by_lua '
    
              local opts = {
                 -- sets the URI of the introspection endpoint
                 introspection_endpoint="https://localhost:9031/oauth2/introspect",
    
                 -- alternatively if your OAuth2 Provider provides a discovery document that contains the
                 -- introspection_endpoint claim you can leave the introspection_endpoint option
                 -- unset and instead use
                 -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",
    
                 client_id="admin",
                 client_secret="demo-password",
                 ssl_verify = "no",
    
                 -- Defines the interval in seconds after which a cached and introspected access token needs
                 -- to be refreshed by introspecting (and validating) it again against the Authorization Server.
                 -- When not defined the value is 0, which means it only expires after the `exp` (or alternative,
                 -- see introspection_expiry_claim) hint as returned by the Authorization Server
                 -- introspection_interval = 60,
    
                 -- Defines the way in which bearer OAuth 2.0 access tokens can be passed to this Resource Server.
                 -- "cookie" as a cookie header called "PA.global" or using the name specified after ":"
                 -- "header" "Authorization: bearer" header
                 -- When not defined the default "Authorization: bearer" header is used
                 -- auth_accept_token_as = "cookie:PA",
    
                 -- If header is used header field is Authorization
                 -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"
    
                 -- Authentication method for the OAuth 2.0 Authorization Server introspection endpoint,
                 -- Used to authenticate the client to the introspection endpoint with a client_id/client_secret
                 -- Defaults to "client_secret_post"
                 -- introspection_endpoint_auth_method = "client_secret_basic",
    
                 -- Specify the names of cookies separated by whitespace to pickup from the browser and send along on backchannel
                 -- calls to the OP and AS endpoints.
                 -- When not defined, no such cookies are sent.
                 -- pass_cookies = "JSESSION"
    
                 -- Defaults to "exp" - Controls the TTL of the introspection cache
                 -- https://tools.ietf.org/html/rfc7662#section-2.2
                 -- introspection_expiry_claim = "exp"
    
                 -- It may be necessary to force an introspection call for an access_token and ignore the existing cached
                 -- introspection results. If so you need to set set the introspection_cache_ignore option to true.
                 -- introspection_cache_ignore = true
    
                 -- optional name of a cache-segment if you need separate
                 -- caches for differently configured locations
                 -- cache_segment = 'api'
              }
    
              -- call introspect for OAuth 2.0 Bearer Access Token validation
              local res, err = require("resty.openidc").introspect(opts)
    
              if err then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
              end
    
              -- at this point res is a Lua table that represents the JSON
              -- object returned from the introspection/validation endpoint
    
              --if res.scope ~= "edit" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
    
              --if res.client_id ~= "ro_client" then
              --  ngx.exit(ngx.HTTP_FORBIDDEN)
              --end
          ';
        }
      }
    }

日志记录

日志记录可以自定义,包括使用自定义日志记录器和重新映射 OpenIDC 的默认日志级别,例如

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

运行测试

我们创建了一个 docker 化的测试设置,以简化依赖项的安装。

要运行测试,请执行以下操作

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

如果您想在测试时创建 luacov 覆盖率,请使用

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

作为第二个命令

支持

有关一般问题,请参阅以下网址的常见问题解答 Wiki 页面:https://github.com/zmartzone/lua-resty-openidc/wiki 任何问题/问题都应发送到 Github Discussons 或 Issues 跟踪器。

免责声明

*此软件由 ZmartZone IAM 开源,但不会以这种方式进行商业支持。任何问题/问题都应发送到 Github Discussons 或 Issues 跟踪器。另请参阅此目录中的 DISCLAIMER 文件。*

作者

Hans Zandbelt (@zandbelt)、Stefan Bodewig (@bodewig)、Bernhard Kuzel (@burnes)

许可证

apache2

依赖项

版本

  • 一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和 OAuth 2.0 资源服务器 (RS) 功能 2023-03-06 09:48:26
  • 一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和 OAuth 2.0 资源服务器 (RS) 功能 2023-03-03 11:13:11
  • 一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和 OAuth 2.0 资源服务器 (RS) 功能 2022-09-14 10:33:12
  • 一个用于 NGINX 的库,实现了 OpenID Connect 依赖方 (RP) 和 OAuth 2.0 资源服务器 (RS) 功能 2022-09-14 10:26:52