lua-resty-openidc

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

$ opm get 040Lab/lua-resty-openidc

[!构建状态](https://travis-ci.org/pingidentity/lua-resty-openidc)

lua-resty-openidc

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

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

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

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

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

依赖项

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

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

如果您作为 OAuth 2.0 资源服务器运行,并且您的访问令牌是 JWT 载荷令牌,并且您希望在本地验证这些令牌(不需要外部调用,请参见下面的第二个配置示例),则需要安装另外两个纯 Lua 依赖项

安装

如果您使用的是 luarocks,请执行以下操作

     luarocks install lua-resty-openidc

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

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;
    
      # 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 and becomes:
                 -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri_path
                 -- unless the scheme is overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
                 redirect_uri_path = "/redirect_uri",
                 discovery = "https://#/.well-known/openid-configuration",
                 client_id = "<client_id>",
                 client_secret = "<client_secret>"
                 --authorization_params = { hd="pingidentity.com" },
                 --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 = "/",
                 --redirect_after_logout_with_id_token_hint = true,
                 --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"],
                 --ssl_verify = "no"
                 --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. 
                 -- This plugin will silently renew the access_token once it is expired if refreshToken scope is present.
    
                 --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)
              }
    
              -- 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 ~= "pingidentity.com" 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;
        }
      }
    }

仅检查身份验证

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

OAuth 2.0 JWT 令牌验证示例配置

用于针对预配置的密钥/秘钥验证 Bearer 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 introspection 10m;
    
      server {
        listen 8080;
    
        location /api {
    
          access_by_lua '
    
              local opts = {
    
                -- 1. example of a shared secret for HS??? signature verification
                --secret = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    
                -- 2. another example of a public cert for RS??? signature verification
                secret = [[-----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-----]],
    
                -- 3. alternatively one can point to a so-called Discovery document that
                -- contains "jwks_uri" entry; the jwks endpoint must provide a x5c entry
                -- discovery = "https://#/.well-known/openid-configuration",
              }
    
              -- 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 JSON
              -- payload in the JWT token
    
              --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 访问令牌的 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"
              }
    
              -- 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
          ';
        }
      }
    }

支持

请参阅以下网址的常见问题解答 Wiki 页面:https://github.com/pingidentity/lua-resty-openidc/wiki 如需商业支持和咨询,请联系:info@zmartzone.eu

任何问题/意见请发送至问题跟踪器或主要作者 hans.zandbelt@zmartzone.eu

免责声明

*此软件由 Ping Identity 开源,但 Ping Identity 不提供商业支持,另请参见此目录中的 DISCLAIMER 文件。如需商业支持,请联系如上所述的 ZmartZone IAM。*

作者

040lab b.v. (@040lab),Hans Zandbelt (@zandbelt)

许可证

apache2

依赖项

版本