openidc: Реализация OpenID Connect Relying Party и OAuth 2.0 Resource Server на Lua для NGINX / nginx-module-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
Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.
Этот документ описывает lua-resty-openidc v1.9.0, выпущенную 1 июля 2026 года.
lua-resty-openidc
lua-resty-openidc — это библиотека для NGINX, реализующая функциональность Relying Party (RP) для OpenID Connect и/или Resource Server (RS) для OAuth 2.0.
При использовании в качестве Relying Party OpenID Connect она аутентифицирует пользователей по отношению к провайдеру OpenID Connect, используя OpenID Connect Discovery и базовый профиль клиента (т.е. поток кода авторизации). При использовании в качестве Resource Server OAuth 2.0 она может проверять токены доступа Bearer OAuth 2.0 по отношению к серверу авторизации или, в случае использования JSON Web Token в качестве токена доступа, проверка может происходить по предварительно настроенному секрету/ключу.
Она поддерживает сессии для аутентифицированных пользователей, используя lua-resty-session, предлагая настраиваемый выбор между хранением состояния сессии в cookie браузера на стороне клиента или использованием серверных механизмов хранения shared-memory|memcache|redis.
Она поддерживает кэширование документов Discovery и проверенных токенов доступа на уровне сервера.
Ее можно использовать в качестве обратного прокси, завершающего OAuth/OpenID Connect перед исходным сервером, чтобы исходный сервер/сервисы могли быть защищены соответствующими стандартами без их реализации на самом сервере.
Пример конфигурации для входа через Google+
Пример конфигурации nginx.conf для аутентификации пользователей через вход в Google+, защищая путь, проксируемый обратным прокси.
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;
# кэш для документов метаданных discovery
lua_shared_dict discovery 1m;
# кэш для JWK
lua_shared_dict jwks 1m;
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",
-- и не могли установить имя хоста
-- Конечная точка discovery OP. Включите, чтобы получить URI всех конечных точек (Token, introspection, logout...)
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
-- Для несовместимых OP с OAuth 2.0 RFC 6749 для аутентификации клиента (см. 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 — это закрытый ключ RSA, который будет использоваться для подписания JWT, сгенерированного lua-resty-openidc для аутентификации к OP
-- client_rsa_private_key_id (необязательно) — это идентификатор ключа, который будет установлен в заголовке JWT для идентификации, какой открытый ключ 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",
-- Алгоритм подписи для JWT клиентского утверждения. По умолчанию RS256 для private_key_jwt и HS256 для client_secret_jwt.
-- Когда token_endpoint_auth_signing_alg_values_supported рекламируется в discovery, настроенное значение должно поддерживаться OP.
--client_jwt_assertion_alg = "PS256",
-- Аудитория для JWT клиентского утверждения. По умолчанию это URL конечной точки, к которой осуществляется вызов.
-- Установите это, когда конечная точка достигается через внутренний URL, но OP ожидает, что его внешний URL конечной точки токена будет аудиторией.
--client_jwt_assertion_audience = "https://op.example.com/token",
-- Время жизни, выраженное в секундах, подписанного JWT, сгенерированного lua-resty-openidc для аутентификации к OP.
-- (используется, когда 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",
-- Подключение keepalive с OP может быть включено ("yes") или отключено ("no").
--keepalive = "no",
--response_mode=form_post может быть использован для того, чтобы lua-resty-openidc использовал [OAuth 2.0 Form Post Response Mode](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` с form_post, если ваш провайдер OpenID Connect и Relying Party не находятся в одном домене.
--authorization_params = { hd="zmartzone.eu" },
-- Когда use_par установлен в true, lua-resty-openidc будет использовать OAuth 2.0 Pushed Authorization Requests (PAR, RFC 9126).
-- Параметры запроса авторизации отправляются на pushed_authorization_request_endpoint из discovery,
-- и браузер перенаправляется на конечную точку авторизации с возвращенным request_uri.
--use_par = false,
-- Переопределите конечную точку PAR из discovery, когда это необходимо.
--pushed_authorization_request_endpoint = "https://MY_OP/pushed_authorization_request",
-- Переопределите метод аутентификации клиента для конечной точки PAR. По умолчанию используется token_endpoint_auth_method.
--pushed_authorization_request_endpoint_auth_method = "client_secret_basic",
-- Когда use_dpop установлен в true, lua-resty-openidc будет отправлять JWT доказательства DPoP (RFC 9449)
-- на конечную точку токена и будет использовать токены доступа, привязанные к DPoP, для userinfo.
-- Начальная реализация DPoP поддерживает доказательства ES256, RS256 и PS256 и повторяет вызовы токена/userinfo
-- один раз, когда OP возвращает вызов DPoP-Nonce.
-- Запросы авторизации включают dpop_jkt для привязки кода авторизации к настроенному открытому ключу DPoP.
-- Ожидается, что ответы токена вернут token_type=DPoP.
-- Если OP предоставляет DPoP-Nonce в успешном ответе токена, он повторно используется для последующих запросов токена.
--use_dpop = false,
--dpop_signing_alg = "ES256",
--dpop_private_key = "PEM-encoded private key matching dpop_signing_alg",
--dpop_public_jwk = {
-- kty = "EC",
-- crv = "P-256",
-- x = "...",
-- y = "..."
--},
--scope = "openid email profile",
-- Обновить id_token пользователя через 900 секунд без необходимости повторной аутентификации
--refresh_session_interval = 900,
--iat_slack = 600,
--redirect_uri_scheme = "https",
--logout_path = "/logout",
--redirect_after_logout_uri = "/",
-- Куда должен быть перенаправлен пользователь после выхода из RP. Эта опция переопределяет любой end_session_endpoint, который OP мог предоставить в ответе discovery.
--redirect_after_logout_with_id_token_hint = true,
-- Должно ли перенаправление после выхода включать id токен в качестве подсказки (если доступно). Эта опция используется только в том случае, если redirect_after_logout_uri установлено.
--redirect_after_logout_with_client_id = true,
-- Должно ли перенаправление после выхода включать client id, когда id_token_hint не отправляется (если доступно).
--post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
-- Куда RP запрашивает, чтобы OP перенаправил пользователя после выхода. Если эта опция установлена на относительный URI, он будет относительным к конечной точке выхода OP, а не RP.
--accept_none_alg = false
-- если ваш провайдер OpenID Connect не подписывает свои id токены
-- (использует алгоритм подписи "none"), тогда установите это значение в true.
--accept_unsupported_alg = true
-- если вы хотите отклонить токены, подписанные с использованием алгоритма,
-- не поддерживаемого lua-resty-jwt, установите это значение в false. Если
-- вы оставите его не установленным или установите в true, подпись токена не будет
-- проверена, когда используется неподдерживаемый алгоритм.
--renew_access_token_on_expiry = true
-- должен ли этот плагин пытаться беззвучно обновить токен доступа, как только он истечет, если доступен refresh token.
-- если обновление токена не удается, пользователь будет перенаправлен на конечную точку авторизации.
--access_token_expires_in = 3600
-- По умолчанию время жизни в секундах токена доступа, если в ответе конечной точки токена отсутствует атрибут expires_in.
--access_token_expires_leeway = 0
-- Льгота по истечению срока действия для обновления токена доступа. Если это установлено, обновление произойдет за access_token_expires_leeway секунд до истечения срока действия токена. Это избегает ошибок в случае, если access_token только что истек при поступлении на ресурсный сервер OAuth.
--force_reauthorize = false
-- Когда force_reauthorize установлен в true, поток авторизации будет выполнен, даже если токен уже был кэширован
--session_contents = {id_token=true}
-- Белый список содержимого сессии для включения. Это можно использовать для уменьшения размера сессии.
-- Когда не установлено, все будет включено в сессию.
-- Доступны:
-- id_token, enc_id_token, user, access_token (включает refresh token)
-- Вы можете указать таймауты для connect/send/read как одно число (устанавливая все таймауты) или как таблицу. Значения указаны в миллисекундах
-- timeout = 1000
-- timeout = { connect = 500, send = 1000, read = 1000 }
--use_nonce = false
-- По умолчанию запрос авторизации включает параметр
-- nonce. Вы можете использовать эту опцию, чтобы отключить его,
-- что может быть необходимо при взаимодействии с неисправным провайдером OpenID
-- Connect, который игнорирует параметр, так как
-- id_token будет отклонен в противном случае.
--authorization_state_expires_in = 300
-- Максимальный возраст в секундах для ожидаемого состояния авторизации, хранящегося
-- в сессии. Истекшие состояния авторизации удаляются, когда
-- создается новый запрос авторизации.
--authorization_state_max_number = 10
-- Максимальное количество ожидаемых состояний авторизации, хранящихся в
-- сессии. При превышении этого количества самые старые состояния авторизации
-- удаляются, когда создается новый запрос авторизации.
--revoke_tokens_on_logout = false
-- Когда revoke_tokens_on_logout установлен в true, выход уведомляет сервер авторизации о том, что ранее полученные refresh и access токены больше не нужны. Это требует, чтобы revocation_endpoint был обнаружим.
-- Если конечная точка отзыва не предоставлена или если возникают ошибки при отзыве, пользователь не будет уведомлен, и процесс выхода продолжится нормально.
-- Необязательно: используйте исходящий прокси для конечных точек провайдера OpenID Connect с таблицей proxy_opts:
-- это требует 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
-- Connect.
--
-- -- Хук `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, будет использоваться "Proof Key for Code Exchange", как
-- определено в RFC 7636. Метод кодового вызова
-- всегда будет S256
}
-- Настройка lua-resty-session
-- Полный список параметров конфигурации задокументирован в репозитории lua-resty-session на GitHub: https://github.com/bungle/lua-resty-session?tab=readme-ov-file#session-configuration
local session_opts = {
-- При использовании cookie для хранения сессий установите общий секрет для шифрования cookie сессии. Это позволяет сессиям оставаться действительными после перезапуска nginx.
-- Это также включает "бессостояние" управление сессиями, так что несколько экземпляров nginx могут обрабатывать запросы без необходимости в "липких" методах балансировки нагрузки.
-- secret = "xxxxxxxxxxxxxxxxxxx",
-- Необязательно, установите префикс cookie, чтобы предотвратить случайное перезаписывание cookie сессии
-- cookie_prefix = "__Host-",
-- Cookie сессии входа должны быть только HTTP
cookie_http_only = true,
-- Cookie сессии входа должны быть помечены как "Secure"
cookie_secure = true,
-- Установите политику cookie same site
cookie_same_site = "Lax",
-- Установите это значение в true, если хотите, чтобы cookie сессии входа сохранялись после перезапуска браузера
remember = true
}
-- вызов аутентификации для аутентификации пользователя OpenID Connect
local res, err = require("resty.openidc").authenticate(opts, nil, nil, session_opts)
if err then
ngx.status = 500
ngx.say(err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- на этом этапе res — это таблица Lua с 3 ключами:
-- id_token : таблица Lua с утверждениями из id_token (обязательно)
-- 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 — это URI, который является частью протокола OpenID Connect. URI перенаправления регистрируется у вашего провайдера OpenID Connect и является URI, на который ваш провайдер перенаправит пользователей после успешного входа. Этот URI затем обрабатывается lua-resty-openidc, где он получает токены и выполняет некоторые проверки, и только после этого браузер перенаправляется туда, куда ваш пользователь хотел пойти изначально.
redirect_uri не должен обрабатываться вашим кодом приложения вообще. Это должен быть URI, за который отвечает lua-resty-openidc, поэтому он должен находиться в location, защищенном lua-resty-openidc.
Вы настраиваете redirect_uri на стороне lua-resty-openidc через параметр opts.redirect_uri (по умолчанию /redirect_uri). Если он начинается с /, lua-resty-openidc добавит протокол и текущее имя хоста к нему при отправке URI провайдеру OpenID Connect (учитывая заголовки HTTP Forwarded и X-Forwarded-*). Но вы также можете указать абсолютный 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 может использовать кэши общей памяти для нескольких вещей. Если вы хотите, чтобы он использовал кэши, вы должны использовать lua_shared_dict в вашем файле nginx.conf.
В настоящее время используется до четырех кэшей
- кэш с именем
discoveryхранит метаданные OpenID Connect Discovery вашего провайдера OpenID Connect. Элементы кэша истекают через 24 часа, если не переопределеныopts.discovery_expires_in(значение, указанное в секундах). Этот кэш будет хранить один элемент на URI эмитента, и вы можете самостоятельно просмотреть документ discovery, чтобы получить оценку необходимого размера — обычно несколько кБ на провайдера OpenID Connect. - кэш с именем
jwksхранит ключевой материал вашего провайдера OpenID Connect, если он предоставляется через конечную точку JWKS. Элементы кэша истекают через 24 часа, если не переопределеныopts.jwks_expires_in. Этот кэш будет хранить один элемент на URI JWKS, и вы можете самостоятельно просмотреть jwks, чтобы получить оценку необходимого размера — обычно несколько кБ на провайдера OpenID Connect. - кэш с именем
introspectionхранит результат интроспекции токена OAuth2. Элементы кэша истекают, когда соответствующий токен истекает. Токены с неизвестным сроком действия вообще не кэшируются. Этот кэш будет содержать одну запись на интроспектируемый токен доступа — обычно это будет несколько кБ на токен. - кэш с именем
jwt_verificationхранит результат проверки JWT. Элементы кэша истекают, когда соответствующий токен истекает. Токены с неизвестным сроком действия не кэшируются в течение двух минут. Этот кэш будет содержать одну запись на проверенный JWT — обычно это будет несколько кБ на токен.
Кэширование результатов интроспекции и проверки JWT
Обратите внимание, что кэши jwt_verification и introspection являются общими между всеми настроенными местоположениями. Если вы используете местоположения с различной конфигурацией opts, общий кэш может позволить токену, который действителен только для одного местоположения, быть принят другим, если он читается из кэша. Чтобы избежать путаницы с кэшем, рекомендуется установить opts.cache_segment на уникальные строки для каждого набора связанных местоположений.
Кэширование отрицательных ответов интроспекции
По умолчанию кэш introspection не будет хранить отрицательные ответы. Это означает, что злонамеренный пользователь может потенциально попытаться исчерпать конечную точку интроспекции, заполняя сервис множеством вызовов с неправильным токеном. Чтобы предотвратить эту ситуацию, opts.introspection_enable_negative_cache может быть установлен в true. Это позволит кэшу introspection хранить отрицательные ответы на время, определенное в поле exp. Кэширование отрицательных ответов интроспекции снизит нагрузку на конечную точку интроспекции, но также подвергнет NGINX атакам на исчерпание ресурсов, так как хранение отрицательных ответов интроспекции будет использовать дополнительное хранилище кэша.
Отзыв токенов
Функция revoke_tokens(opts, session) отзывает текущий refresh и access токены. В отличие от полного выхода, cookie сессии не будут уничтожены, и конечная точка endsession не будет вызвана. Функция возвращает true, если оба токена были успешно отозваны. Эта функция может быть полезна в сценариях, когда вы хотите уничтожить/удалить сессию со стороны сервера.
С помощью revoke_token(opts, token_type_hint, token) также можно отозвать конкретный токен. token_type_hint обычно может быть refresh_token или access_token.
Пример конфигурации для проверки токенов JWT OAuth 2.0
Пример конфигурации nginx.conf для проверки токенов доступа Bearer JWT по отношению к предварительно настроенному секрету/ключу. После успешной проверки сервер 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",
-- либо обе записи "n" и "e" для проверки подписи RSA
-- 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
-- необ optional имя сегмента кэша, если вам нужны отдельные
-- кэши для по-разному настроенных местоположений
-- cache_segment = 'api'
}
-- вызов bearer_jwt_verify для проверки JWT 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 "no access_token provided")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- на этом этапе res — это таблица Lua, представляющая (проверенный) JSON
-- полезную нагрузку в токене JWT; теперь мы обычно не хотим разрешать любой
-- токен, выданный сервером авторизации, но мы хотим применить
-- некоторые ограничения доступа через идентификаторы клиентов или области
--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
Пример конфигурации nginx.conf для проверки токенов доступа Bearer по отношению к серверу авторизации 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;
# кэш для результатов проверки
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" - управляет временем жизни кэша интроспекции
-- https://tools.ietf.org/html/rfc7662#section-2.2
-- introspection_expiry_claim = "exp"
-- необ optional имя сегмента кэша, если вам нужны отдельные
-- кэши для по-разному настроенных местоположений
-- cache_segment = 'api'
}
-- вызов introspect для проверки токена доступа Bearer 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
-- на этом этапе res — это таблица Lua, представляющая JSON
-- объект, возвращенный из конечной точки интроспекции/проверки
--if res.scope ~= "edit" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
--if res.client_id ~= "ro_client" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
';
}
}
}
Пример конфигурации для передачи токенов доступа Bearer OAuth 2.0 в виде cookie
Пример конфигурации nginx.conf для проверки токенов доступа Bearer, передаваемых в виде cookie, по отношению к серверу авторизации 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;
# кэш для результатов проверки
lua_shared_dict introspection 10m;
server {
listen 8080;
location /api {
access_by_lua '
local opts = {
-- устанавливает URI конечной точки интроспекции
introspection_endpoint="https://localhost:9031/oauth2/introspect",
-- в качестве альтернативы, если ваш провайдер OAuth2 предоставляет документ discovery, который содержит
-- утверждение 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" как заголовок cookie, называемый "PA.global" или с использованием имени, указанного после ":"
-- "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",
-- Укажите имена cookie, разделенные пробелами, чтобы забрать из браузера и отправить вместе на обратные каналы
-- вызовы к конечным точкам OP и AS.
-- Когда не определено, такие cookie не отправляются.
-- pass_cookies = "JSESSION"
-- По умолчанию "exp" - управляет временем жизни кэша интроспекции
-- https://tools.ietf.org/html/rfc7662#section-2.2
-- introspection_expiry_claim = "exp"
-- Может потребоваться принудительный вызов интроспекции для access_token и игнорирование существующих кэшированных
-- результатов интроспекции. Если это так, вам нужно установить опцию introspection_cache_ignore в true.
-- introspection_cache_ignore = true
-- необ optional имя сегмента кэша, если вам нужны отдельные
-- кэши для по-разному настроенных местоположений
-- cache_segment = 'api'
}
-- вызов introspect для проверки токена доступа Bearer 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
-- на этом этапе res — это таблица Lua, представляющая JSON
-- объект, возвращенный из конечной точки интроспекции/проверки
--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 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.
Отказ от ответственности
Это программное обеспечение открыто предоставлено ZmartZone IAM, но не поддерживается коммерчески как таковое. Все вопросы/проблемы следует направлять в обсуждения или трекер проблем на GitHub. Смотрите также файл DISCLAIMER в этой директории.
GitHub
Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-openidc.
