jwt: JWT Para El Gran nginx-module-lua
Instalación
Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.
CentOS/RHEL 7 o 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-jwt
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-jwt
Para usar esta biblioteca Lua con NGINX, asegúrate de que el nginx-module-lua esté instalado.
Este documento describe lua-resty-jwt v0.1.11 lanzado el 11 de julio de 2017.
Esta biblioteca requiere una compilación de nginx con OpenSSL, el módulo ngx_lua, el LuaJIT 2.0, el lua-resty-hmac, y el lua-resty-string.
Sinopsis
# nginx.conf:
server {
default_type text/plain;
location = /verify {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
".eyJmb28iOiJiYXIifQ" ..
".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
ngx.say(cjson.encode(jwt_obj))
';
}
location = /sign {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = jwt:sign(
"lua-resty-jwt",
{
header={typ="JWT", alg="HS256"},
payload={foo="bar"}
}
)
ngx.say(jwt_token)
';
}
}
Métodos
Para cargar esta biblioteca,
- necesitas especificar la ruta de esta biblioteca en la directiva lua_package_path de ngx_lua. Por ejemplo,
lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";. - usas
requirepara cargar la biblioteca en una variable local de Lua:
local jwt = require "resty.jwt"
sign
syntax: local jwt_token = jwt:sign(key, table_of_jwt)
firma un table_of_jwt a un jwt_token.
El argumento alg especifica qué algoritmo de hash usar (HS256, HS512, RS256).
ejemplo de table_of_jwt
{
"header": {"typ": "JWT", "alg": "HS512"},
"payload": {"foo": "bar"}
}
verify
syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])
verifica un jwt_token y devuelve una tabla jwt_obj. key puede ser una clave precompartida (como una cadena), o una función que toma un solo parámetro (el valor de kid del encabezado) y devuelve ya sea la clave precompartida (como una cadena) para el kid o nil si la búsqueda del kid falló. Esta llamada fallará si intentas especificar una función para key y no hay un kid existente en el encabezado.
Consulta Verificación para más detalles sobre el formato de los parámetros claim_spec.
load & verify
syntax: local jwt_obj = jwt:load_jwt(jwt_token)
syntax: local verified = jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]])
verify = load_jwt + verify_jwt_obj
carga jwt, verifica el kid, y luego lo verifica con la clave correcta.
ejemplo de jwt_obj
{
"raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
"raw_payload": "eyJmb28iOiJiYXIifQ",
"signature": "wrong-signature",
"header": {"typ": "JWT", "alg": "HS256"},
"payload": {"foo": "bar"},
"verified": false,
"valid": true,
"reason": "signature mismatched: wrong-signature"
}
sign-jwe
syntax: local jwt_token = jwt:sign(key, table_of_jwt)
firma un table_of_jwt a un jwt_token.
El argumento alg especifica qué algoritmo de hash usar para cifrar la clave (dir). El argumento enc especifica qué algoritmo de hash usar para cifrar la carga útil (A128CBC-HS256, A256CBC-HS512).
ejemplo de table_of_jwt
{
"header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"},
"payload": {"foo": "bar"}
}
Verificación
Tanto las funciones jwt:load como jwt:verify_jwt_obj toman, como parámetros adicionales, cualquier número de parámetros opcionales claim_spec. Un claim_spec es simplemente una tabla lua de reclamaciones y validadores. Cada clave en la tabla claim_spec corresponde a una clave coincidente en la carga útil, y el validator es una función que se llamará para determinar si se cumplen las reclamaciones.
La firma de una función validator es:
function(val, claim, jwt_json)
Donde val es el valor de la reclamación del jwt_obj que se está probando (o nil si no existe en la carga útil del objeto), claim es el nombre de la reclamación que se está verificando, y jwt_json es una representación serializada en json del objeto que se está verificando. Si la función no necesita los parámetros claim o jwt_json, pueden omitirse.
Una función validator devuelve true o false. Cualquier validator PUEDE generar un error, y la validación se tratará como un fallo, y el error que se generó se colocará en el campo de razón del objeto resultante. Si un validator no devuelve nada (es decir, nil), entonces la función se considera que ha tenido éxito, bajo la suposición de que habría generado un error si hubiera fallado.
Una reclamación especial llamada __jwt puede ser utilizada de tal manera que si existe una función validator para ella, entonces el validator será llamado con una copia profunda de todo el objeto jwt analizado como el valor de val. Esto es para que puedas escribir verificaciones para un objeto completo que puede depender de una o más reclamaciones.
Se pueden especificar múltiples tablas claim_spec a las funciones jwt:load y jwt:verify_jwt_obj - y se ejecutarán en orden. No hay garantía del orden de ejecución de los validators individuales dentro de un solo claim_spec. Si un claim_spec falla, entonces cualquier claim_spec siguiente NO se ejecutará.
ejemplo de claim_spec
{
sub = function(val) return string.match("^[a-z]+$", val) end,
iss = function(val)
for _, value in pairs({ "first", "second" }) do
if value == val then return true end
end
return false
end,
__jwt = function(val, claim, jwt_json)
if val.payload.foo == nil or val.payload.bar == nil then
error("Need to specify either 'foo' or 'bar'")
end
end
}
Validadores JWT
Una biblioteca de funciones validator útiles existe en resty.jwt-validators. Puedes usar esta biblioteca incluyendo:
local validators = require "resty.jwt-validators"
Las siguientes funciones están actualmente definidas en la biblioteca de validadores. Aquellas marcadas con "(opt)" significan que existe la misma función llamada opt_<name> que toma los mismos parámetros. La versión "opt" de la función devolverá true si la clave no existe en la carga útil del jwt_object que se está verificando, mientras que la versión "no opt" de la función devolverá false si la clave no existe en la carga útil del jwt_object que se está verificando.
validators.chain(...)
Devuelve un validador que encadena las funciones dadas, una tras otra, siempre que sigan pasando sus verificaciones.
validators.required(chain_function)
Devuelve un validador que devuelve false si un valor no existe. Si el valor existe y se especifica una chain_function, entonces se devolverá el valor de chain_function(val, claim, jwt_json), de lo contrario, se devolverá true. Esto permite especificar que un valor es tanto requerido como que debe coincidir con alguna verificación adicional.
validators.require_one_of(claim_keys)
Devuelve un validador que genera un error con un mensaje si NINGUNA de las claves de reclamación dadas existen. Se espera que esta función se use contra un objeto jwt completo. Las claim_keys deben ser una tabla no vacía de cadenas.
validators.check(check_val, check_function, name, check_type) (opt)
Devuelve un validador que verifica si el resultado de llamar a la check_function dada para el valor probado y check_val devuelve true. El valor de check_val y check_function no pueden ser nil. El nombre opcional se utiliza para mensajes de error y se establece de forma predeterminada en "check_value". El tipo de verificación opcional se utiliza para asegurarse de que el tipo de verificación coincida y se establece de forma predeterminada en type(check_val). El primer parámetro pasado a check_function nunca será nil. Si la check_function genera un error, eso se añadirá al mensaje de error.
validators.equals(check_val) (opt)
Devuelve un validador que verifica si un valor es exactamente igual (usando ==) al valor de verificación dado. El valor de check_val no puede ser nil.
validators.matches(pattern) (opt)
Devuelve un validador que verifica si un valor coincide con el patrón dado (usando string.match). El valor de pattern debe ser una cadena.
validators.any_of(check_values, check_function, name, check_type, table_type) (opt)
Devuelve un validador que llama a la check_function dada para cada uno de los check_values dados y el valor probado. Si alguna de estas llamadas devuelve true, entonces esta función devuelve true. El valor de check_values debe ser una tabla no vacía con todos los mismos tipos, y el valor de check_function no debe ser nil. El nombre opcional se utiliza para mensajes de error y se establece de forma predeterminada en "check_values". El tipo de verificación opcional se utiliza para asegurarse de que el tipo de verificación coincida y se establece de forma predeterminada en type(check_values[1]) - el tipo de tabla.
validators.equals_any_of(check_values) (opt)
Devuelve un validador que verifica si un valor es exactamente igual a cualquiera de los check_values dados.
validators.matches_any_of(patterns) (opt)
Devuelve un validador que verifica si un valor coincide con cualquiera de los patterns dados.
validators.contains_any_of(check_values,name) (opt)
Devuelve un validador que verifica si un valor del tipo esperado string existe en cualquiera de los check_values dados. El valor de check_values debe ser una tabla no vacía con todos los mismos tipos. El nombre opcional se utiliza para mensajes de error y se establece de forma predeterminada en check_values.
validators.greater_than(check_val) (opt)
Devuelve un validador que verifica cómo se compara un valor (numéricamente, usando >) con un check_value dado. El valor de check_val no puede ser nil y debe ser un número.
validators.greater_than_or_equal(check_val) (opt)
Devuelve un validador que verifica cómo se compara un valor (numéricamente, usando >=) con un check_value dado. El valor de check_val no puede ser nil y debe ser un número.
validators.less_than(check_val) (opt)
Devuelve un validador que verifica cómo se compara un valor (numéricamente, usando <) con un check_value dado. El valor de check_val no puede ser nil y debe ser un número.
validators.less_than_or_equal(check_val) (opt)
Devuelve un validador que verifica cómo se compara un valor (numéricamente, usando <=) con un check_value dado. El valor de check_val no puede ser nil y debe ser un número.
validators.is_not_before() (opt)
Devuelve un validador que verifica si la hora actual no es anterior al valor probado dentro de la tolerancia del sistema. Esto significa que:
val <= (system_clock() + system_leeway).
validators.is_not_expired() (opt)
Devuelve un validador que verifica si la hora actual no es igual o posterior al valor probado dentro de la tolerancia del sistema. Esto significa que:
val > (system_clock() - system_leeway).
validators.is_at() (opt)
Devuelve un validador que verifica si la hora actual es la misma que el valor probado dentro de la tolerancia del sistema. Esto significa que:
val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway).
validators.set_system_leeway(leeway)
Una función para establecer la tolerancia (en segundos) utilizada para is_not_before y is_not_expired. El valor predeterminado es usar 0 segundos.
validators.set_system_clock(clock)
Una función para establecer el reloj del sistema utilizado para is_not_before y is_not_expired. El valor predeterminado es usar ngx.now.
ejemplo de claim_spec usando validadores
local validators = require "resty.jwt-validators"
local claim_spec = {
sub = validators.opt_matches("^[a-z]+$"),
iss = validators.equals_any_of({ "first", "second" }),
__jwt = validators.require_one_of({ "foo", "bar" })
}
Opciones de legado/período de tiempo
Para admitir código que utilizaba versiones anteriores de esta biblioteca, así como para simplificar la especificación de claim_specs basados en períodos de tiempo, puedes usar en lugar de cualquier parámetro único claim_spec una tabla de validation_options. El parámetro debe expresarse como una tabla clave/valor. Cada clave de la tabla debe seleccionarse de la siguiente lista.
Al usar validation_options de legado, SOLO debes especificar estas opciones. Es decir, no puedes mezclar validation_options de legado con otros validadores claim_spec. Para lograr eso, debes especificar múltiples opciones a las funciones jwt:load/jwt:verify_jwt_obj.
-
lifetime_grace_period: Define la tolerancia en segundos para tener en cuenta la discrepancia de reloj entre el servidor que generó el jwt y el servidor que lo valida. El valor debe ser cero (0) o un entero positivo.-
Cuando se especifica esta opción de validación, el proceso asegurará que el jwt contenga al menos una de las dos reclamaciones
nbfoexpy comparará la hora actual del reloj con esos límites. Si el jwt se considera expirado o no válido, la verificación fallará. -
Cuando no se pueden encontrar las reclamaciones
nbfyexp, la verificación fallará. -
Se espera que las reclamaciones
nbfyexpse expresen en el jwt como valores numéricos. Si no es así, la verificación fallará. -
Especificar esta opción es equivalente a llamar a:
validators.set_system_leeway(leeway)
y especificar como un
claim_spec:{ __jwt = validators.require_one_of({ "nbf", "exp" }), nbf = validators.opt_is_not_before(), exp = validators.opt_is_not_expired() } -
-
require_nbf_claim: Expresa si la reclamaciónnbfes opcional o no. El valor debe ser un booleano.-
Cuando esta opción de validación se establece en
truey no se ha especificado unlifetime_grace_period, se implica una tolerancia de cero (0). -
Especificar esta opción es equivalente a especificar como un
claim_spec:{ nbf = validators.is_not_before(), }
-
-
require_exp_claim: Expresa si la reclamaciónexpes opcional o no. El valor debe ser un booleano.-
Cuando esta opción de validación se establece en
truey no se ha especificado unlifetime_grace_period, se implica una tolerancia de cero (0). -
Especificar esta opción es equivalente a especificar como un
claim_spec:{ exp = validators.is_not_expired(), }
-
-
valid_issuers: Lista blanca de los emisores verificados del jwt. El valor debe ser un array de cadenas.-
Cuando se especifica esta opción de validación, el proceso comparará la reclamación
issdel jwt con la lista de emisores válidos. La comparación se realiza de manera sensible a mayúsculas y minúsculas. Si el emisor del jwt no se encuentra en la lista blanca, la verificación fallará. -
Se espera que la reclamación
issse exprese en el jwt como una cadena. Si no es así, la verificación fallará. -
Especificar esta opción es equivalente a especificar como un
claim_spec:{ iss = validators.equals_any_of(valid_issuers), }
-
ejemplo de uso de validation_options
local jwt_obj = jwt:verify(key, jwt_token,
{
lifetime_grace_period = 120,
require_exp_claim = true,
valid_issuers = { "my-trusted-issuer", "my-other-trusteed-issuer" }
}
)
Ejemplos
Pruebas Con Docker
./ci script
Ver También
- el módulo ngx_lua: http://wiki.nginx.org/HttpLuaModule
GitHub
Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-jwt.