跳转至

openidc: OpenID Connect Relying Party 和 OAuth 2.0 资源服务器在 NGINX / nginx-module-lua 中的 Lua 实现

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 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

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-openidc v1.8.0,于 2024 年 9 月 13 日发布。


CI 状态 OpenID 认证

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 和基本客户端配置(即授权码流程)对用户进行身份验证。作为 OAuth 2.0 资源服务器使用时,它可以针对授权服务器验证 OAuth 2.0 Bearer 访问令牌,或者在使用 JSON Web Token 作为访问令牌时,可以针对预配置的密钥/秘密进行验证。

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

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

它可以用作反向代理,在源服务器前终止 OAuth/OpenID Connect,从而保护源服务器/服务,遵循相关标准,而无需在服务器本身实现这些标准。

Google+ 登录的示例配置

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

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;

  # 用于发现元数据文档的缓存
  lua_shared_dict discovery 1m;
  # 用于 JWK 的缓存
  lua_shared_dict jwks 1m;

  # 注意:如果您有 "lua_code_cache off;",请使用:
  # set $session_secret xxxxxxxxxxxxxxxxxxx;
  # 参见:https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- 完整的重定向 URI 必须由此脚本保护
             -- 如果 URI  / 开头,完整的重定向 URI 变为
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- 除非使用 opts.redirect_uri_scheme 或传入请求中的 X-Forwarded-Proto 头覆盖了方案
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- 在版本 1.6.1 之前,您会指定
             -- redirect_uri_path = "/redirect_uri",
             -- 并且无法设置主机名

             -- OP 的发现端点。启用以获取所有端点的 URI(令牌、检查、注销...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- 访问 OP 令牌端点需要身份验证。支持多种身份验证模式:
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o 如果 token_endpoint_auth_method 设置为 "client_secret_basic"、"client_secret_post"  "client_secret_jwt",则对令牌端点的身份验证使用 client_id  client_secret
             --   对于不符合 OAuth 2.0 RFC 6749  OP(参见 https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id  client_secret  URL 编码时必须保持不变
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o 如果 token_endpoint_auth_method 设置为 "private_key_jwt",则对令牌端点的身份验证使用 client_id、client_rsa_private_key  client_rsa_private_key_id 来计算签名的 JWT
             --   client_rsa_private_key 是用于签署 lua-resty-openidc 生成的 JWT  RSA 私钥,用于对 OP 进行身份验证
             --   client_rsa_private_key_id(可选)是要在 JWT 头中设置的密钥 ID,以标识 OP 应使用哪个公钥来验证 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",
             --    lua-resty-openidc 生成的用于对 OP 进行身份验证的签名 JWT 的有效期(以秒为单位)。
             --   (在 token_endpoint_auth_method 设置为 "private_key_jwt"  "client_secret_jwt" 身份验证时使用)。默认是 60 秒。
             --client_jwt_assertion_expires_in = 60,
             -- 使用 https 访问任何 OP 端点时,可以强制 SSL 证书检查("yes")或不强制("no")。
             --ssl_verify = "no",
             --  OP 的连接保持活动可以启用("yes")或禁用("no")。
             --keepalive = "no",

             --response_mode=form_post 可用于使 lua-resty-openidc 使用 [OAuth 2.0 表单提交响应模式](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html)。*注意* 对于现代浏览器,您需要将 [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) 设置为 `None`,除非您的 OpenID Connect 提供者和 Relying Party 共享相同的域。
             --authorization_params = { hd="zmartzone.eu" },
             --scope = "openid email profile",
             -- 在不需要重新身份验证的情况下,每 900 秒刷新用户的 id_token
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             -- 用户从 RP 注销后应重定向到哪里。此选项覆盖 OP 在发现响应中可能提供的任何 end_session_endpoint。
             --redirect_after_logout_with_id_token_hint = true,
             -- 注销后重定向是否应包含 id token 作为提示(如果可用)。此选项仅在设置了 redirect_after_logout_uri 时使用。
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- RP 请求 OP 在注销后重定向用户到哪里。如果此选项设置为相对 URI,则相对于 OP 的注销端点,而不是 RP 的。

             --accept_none_alg = false
             -- 如果您的 OpenID Connect 提供者不签署其 id tokens
             -- (使用 "none" 签名算法),则将其设置为 true。

             --accept_unsupported_alg = true
             -- 如果您想拒绝使用 lua-resty-jwt 不支持的算法签名的令牌,则将其设置为 false。如果
             -- 您不设置或将其设置为 true,则在使用不支持的算法时不会验证令牌签名。

             --renew_access_token_on_expiry = true
             -- 此插件是否应尝试在访问令牌过期后静默续订访问令牌,如果有可用的刷新令牌。
             -- 如果续订令牌失败,用户将被重定向到授权端点。
             --access_token_expires_in = 3600
             -- 如果令牌端点响应中没有 expires_in 属性,则 access_token 的默认生命周期(以秒为单位)。

             --access_token_expires_leeway = 0
             -- 访问令牌续订的过期宽限。如果设置,则续订将在访问令牌过期前 access_token_expires_leeway 秒进行。这避免了在访问令牌刚好在到达 OAuth 资源服务器时过期的错误。

             --force_reauthorize = false
             --  force_reauthorize 设置为 true 时,即使已经缓存了令牌,也将执行授权流程
             --session_contents = {id_token=true}
             -- 要启用的会话内容白名单。这可以用来减少会话大小。
             -- 未设置时,所有内容都将包含在会话中。
             -- 可用的有:
             -- id_token, enc_id_token, user, access_token(包括刷新令牌)

             -- 您可以为连接/发送/读取指定超时,作为单个数字(设置所有超时)或作为表。值以毫秒为单位
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- 默认情况下,授权请求包括 nonce 参数。您可以使用此选项禁用它
             -- 这在与忽略参数的损坏 OpenID 连接提供者交互时可能是必要的,因为
             -- 否则 id_token 将被拒绝。

             --revoke_tokens_on_logout = false
             --  revoke_tokens_on_logout 设置为 true 时,注销会通知授权服务器,之前获得的刷新和访问令牌不再需要。这要求撤销端点是可发现的。
             -- 如果未提供撤销端点或撤销时出现错误,用户将不会被通知,注销过程将正常继续。

             -- 可选:使用代理选项表为 OpenID Connect 提供者端点添加出站代理:
             -- 这需要 lua-resty-http >= 0.12
             -- proxy_opts = {
             --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
             --    https_proxy = "http://<proxy_host>:<proxy_port>/"
             -- }

             -- 生命周期钩子
             --
             -- lifecycle = {
             --    on_created = handle_created,
             --    on_authenticated = handle_authenticated,
             --    on_regenerated = handle_regenerated
             --    on_logout = handle_logout
             -- }
             --
             -- 其中 `handle_created`、`handle_authenticated`、`handle_regenerated`  `handle_logout` 是可调用的
             -- 接受参数 `session`。`handle_created` 还接受第二个参数 `params`,这是一个表
             -- 包含用于将用户重定向到 OpenID 连接提供者端点的授权请求的查询参数。
             --
             --  -- `on_created` 钩子在
             --     `openidc_authorize` 中创建会话后立即调用,在保存会话之前
             --  -- `on_authenticated` 钩子在
             --     `openidc_authorization_response` 中接收到授权响应后立即调用,在保存会话之前
             --      lua-resty-openidc 1.7.5 开始,这将接收解码的 id_token 作为第二个参数,令牌端点的响应作为第三个参数
             --  -- `on_regenerated` 
             --     通过令牌刷新获得新访问令牌后立即调用,并使用重新生成的会话表调用
             --  -- `on_logout` 钩子在
             --     `openidc_logout` 中销毁会话之前调用
             --
             --  可以使用任何、所有或没有钩子。空的 `lifecycle` 不执行任何操作。
             --  返回真值的钩子会导致它们参与的生命周期操作失败。

             -- 可选:为 HTTP 请求添加装饰器
             --  lua-resty-openidc 直接与 OpenID Connect
             -- 提供者通信时应用。可用于提供额外的 HTTP 
             -- 或添加其他类似行为。
             -- 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,
             -- 设置为 true 时,将使用 RFC 7636 中定义的 "Proof Key for Code Exchange"。代码挑战方法将始终为 S256

          }

          -- 调用 authenticate 进行 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

          -- 此时 res 是一个包含 3 个键的 Lua 表:
          --   id_token    : 包含 id_token 中声明的 Lua 表(必需)
          --   access_token: 访问令牌(可选)
          --   user        : 从用户信息端点返回的声明的 Lua 表(可选)

          --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

          -- 设置带有用户信息的头:这将覆盖任何现有的头
          -- 但在令牌中未提供值时也会清除它们(!)
          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 参数在 lua-resty-openidc 端配置 redirect_uri(默认为 /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-openidc 将拦截对该路径的请求,而不是 opts.redirect_uri 的路径。

仅检查身份验证

-- 检查会话,但如果尚未登录,则不重定向到身份验证
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

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

-- 检查会话,如果尚未登录则不重定向到身份验证,而是返回错误
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 可以使用 共享内存缓存 来处理多项内容。如果您希望它使用缓存,必须在 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 不会被销毁,结束会话端点也不会被调用。该函数在两个令牌成功撤销时返回 true。在您希望从服务器端销毁/删除会话的场景中,此函数可能会很有帮助。

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

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

用于验证 Bearer JWT 访问令牌与预配置的密钥/秘密的示例 nginx.conf 配置。 一旦成功验证,NGINX 服务器可以作为内部源服务器的反向代理。

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  # 用于 JWT 验证结果的缓存
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. HS??? 签名验证的共享密钥示例
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- 在版本 1.6.1 之前,此选项的密钥将是 secret
            -- 而不是 symmetric_key

            -- 2. 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-----]],
            -- 在版本 1.6.1 之前,此选项的密钥将是 secret
            -- 而不是 public_key

            -- 3. 或者可以指向所谓的 Discovery 文档,该文档
            -- 包含 "jwks_uri" 条目;jwks 端点必须提供 "x5c" 条目
            --  RSA 签名验证的 "n" 模数和 "e" 指数条目
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- 您期望使用的签名算法;
             -- 可以是单个字符串或表。
             -- 出于安全原因,您应该为此设置
             -- 以避免接受声称由 HMAC 签名的令牌
             -- 使用公钥 RSA。
             --token_signing_alg_values_expected = { "RS256" }

             -- 如果您想接受未签名的令牌(使用
             -- "none" 签名算法),则将其设置为 true。
             --accept_none_alg = false

             -- 如果您想拒绝使用 lua-resty-jwt 不支持的算法签名的令牌,则将其设置为 false。如果
             -- 您不设置,则不会验证令牌签名。
             --accept_unsupported_alg = true

             -- jwk 缓存的过期时间(以秒为单位),默认是 1 天。
             --jwk_expires_in = 24 * 60 * 60

             -- 可能需要强制验证 Bearer 令牌并忽略现有的缓存
             -- 验证结果。如果是这样,您需要将 jwt_verification_cache_ignore 选项设置为 true。
             -- jwt_verification_cache_ignore = true

             -- 如果您需要为不同配置的位置设置单独的缓存,则可选缓存段名称
             -- cache_segment = 'api'
          }

          -- 调用 bearer_jwt_verify 进行 OAuth 2.0 JWT 验证
          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 "未提供 access_token")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- 此时 res 是一个表示(验证过的)JSON  Lua 
          -- 负载在 JWT 令牌中;现在我们通常不想允许任何
          -- 由授权服务器签发的令牌,而是希望通过客户端 ID 或作用域应用
          -- 一些访问限制

          --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 的示例配置

用于验证 Bearer 访问令牌与 PingFederate OAuth 2.0 授权服务器的示例 nginx.conf 配置。

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;

  # 用于验证结果的缓存
  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",

             -- 默认为 "exp" - 控制 introspection 缓存的 TTL
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- 如果您需要为不同配置的位置设置单独的缓存,则可选缓存段名称
             -- cache_segment = 'api'
          }

          -- 调用 introspect 进行 OAuth 2.0 Bearer 访问令牌验证
          local res, err = require("resty.openidc").introspect(opts)

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

          -- 此时 res 是一个表示 JSON  Lua 
          -- 从检查/验证端点返回的对象

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

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

用于验证作为 Cookie 传递的 Bearer 访问令牌与 ORY/Hydra 授权服务器的示例 nginx.conf 配置。

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;

  # 用于验证结果的缓存
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- 设置检查端点的 URI
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- 或者,如果您的 OAuth2 提供者提供包含
             -- introspection_endpoint 声明的发现文档,您可以将 introspection_endpoint 选项
             -- 保持未设置,而是使用
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

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

             -- 定义在缓存的访问令牌需要
             -- 通过再次检查(和验证)它来刷新之前的时间间隔(以秒为单位)。
             -- 未定义时,值为 0,这意味着它仅在授权服务器返回的 `exp`(或替代值,
             -- 参见 introspection_expiry_claim)后过期
             -- introspection_interval = 60,

             -- 定义 Bearer OAuth 2.0 访问令牌可以传递给此资源服务器的方式。
             -- "cookie" 作为名为 "PA.global"  Cookie 头,或使用在 ":" 后指定的名称
             -- "header" "Authorization: bearer" 
             -- 未定义时,使用默认的 "Authorization: bearer" 
             -- auth_accept_token_as = "cookie:PA",

             -- 如果使用头,则头字段为 Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- OAuth 2.0 授权服务器检查端点的身份验证方法,
             -- 用于使用 client_id/client_secret 对客户端进行身份验证
             -- 默认为 "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- 指定要从浏览器中提取并在与 OP  AS 端点的后端通话中发送的 Cookie 名称,以空格分隔。
             -- 未定义时,不发送此类 Cookie。
             -- pass_cookies = "JSESSION"

             -- 默认为 "exp" - 控制 introspection 缓存的 TTL
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- 可能需要强制对 access_token 进行检查并忽略现有的缓存
             -- 检查结果。如果是这样,您需要将 introspection_cache_ignore 选项设置为 true。
             -- introspection_cache_ignore = true

             -- 如果您需要为不同配置的位置设置单独的缓存,则可选缓存段名称
             -- cache_segment = 'api'
          }

          -- 调用 introspect 进行 OAuth 2.0 Bearer 访问令牌验证
          local res, err = require("resty.openidc").introspect(opts)

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

          -- 此时 res 是一个表示 JSON  Lua 
          -- 从检查/验证端点返回的对象

          --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

作为第二个命令

支持

有关常见问题的通用问题,请参阅维基页面中的常见问题解答: https://github.com/zmartzone/lua-resty-openidc/wiki 任何问题/问题应提交至 GitHub 讨论或问题跟踪器。

免责声明

此软件由 ZmartZone IAM 开源,但不提供商业支持。 任何问题/问题应提交至 GitHub 讨论或问题跟踪器。 另请参阅此目录中的免责声明文件。

GitHub

您可以在 nginx-module-openidc 的 GitHub 仓库 中找到有关此模块的其他配置提示和文档。