lua-resty-openidc

一个用于 NGINX 的库,实现了 OpenID Connect Relying Party (RP) 和 OAuth 2.0 Resource Server (RS) 功能

$ opm get bsiara/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 Relying Party (RP) 和/或 OAuth 2.0 Resource Server (RS) 功能。

当用作 OpenID Connect Relying Party 时,它使用 OpenID Connect Discovery 和基本客户端配置文件(即授权码流程)针对 OpenID Connect 提供程序验证用户。当用作 OAuth 2.0 Resource Server 时,它可以针对授权服务器验证 OAuth 2.0 Bearer Access Token,或者,如果 Access Token 使用 JSON Web Token,则可以针对预配置的密钥/秘钥进行验证。

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

它支持已解析的 Discovery 文档和已验证的 Access Token 的服务器范围缓存。

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

依赖项

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

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

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

仅当作为 OAuth 2.0 Resource Server(仅)运行并使用远程自省进行访问令牌验证时,才不需要上述 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",
                 --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 Discovery 元数据。缓存项在 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 令牌验证示例配置

针对预配置的密钥/秘钥验证 Bearer JWT Access Token 的 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 授权服务器验证 Bearer Access Token 的 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 传递的 Bearer Access Token 的 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)

许可证

apache2

依赖项

版本

  • 一个用于 NGINX 的库,实现了 OpenID Connect Relying Party (RP) 和 OAuth 2.0 Resource Server (RS) 功能 2023-07-27 09:05:51