Skip to content

Commit 5214b9c

Browse files
feat(jwt): support more algorithm
Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
1 parent 4bc4e2c commit 5214b9c

File tree

4 files changed

+802
-59
lines changed

4 files changed

+802
-59
lines changed

apisix/plugins/jwt-auth.lua

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@
1515
-- limitations under the License.
1616
--
1717
local core = require("apisix.core")
18-
local jwt = require("resty.jwt")
1918
local consumer_mod = require("apisix.consumer")
19+
local resty_random = require("resty.random")
2020
local new_tab = require ("table.new")
2121
local auth_utils = require("apisix.utils.auth")
2222

2323
local ngx_decode_base64 = ngx.decode_base64
24+
local ngx_encode_base64 = ngx.encode_base64
2425
local ngx = ngx
26+
local ngx_time = ngx.time
2527
local sub_str = string.sub
2628
local table_insert = table.insert
2729
local table_concat = table.concat
2830
local ngx_re_gmatch = ngx.re.gmatch
2931
local plugin_name = "jwt-auth"
3032
local schema_def = require("apisix.schema_def")
31-
33+
local jwt_parser = require("apisix.plugins.jwt-auth.parser")
3234

3335
local schema = {
3436
type = "object",
@@ -60,6 +62,15 @@ local schema = {
6062
},
6163
realm = schema_def.get_realm_schema("jwt"),
6264
anonymous_consumer = schema_def.anonymous_consumer_schema,
65+
claims_to_verify = {
66+
type = "array",
67+
items = {
68+
type = "string",
69+
enum = {"exp","nbf"},
70+
},
71+
uniqueItems = true,
72+
default = {"exp", "nbf"},
73+
},
6374
},
6475
}
6576

@@ -77,7 +88,21 @@ local consumer_schema = {
7788
},
7889
algorithm = {
7990
type = "string",
80-
enum = {"HS256", "HS512", "RS256", "ES256"},
91+
enum = {
92+
"HS256",
93+
"HS384",
94+
"HS512",
95+
"RS256",
96+
"RS384",
97+
"RS512",
98+
"ES256",
99+
"ES384",
100+
"ES512",
101+
"PS256",
102+
"PS384",
103+
"PS512",
104+
"EdDSA",
105+
},
81106
default = "HS256"
82107
},
83108
exp = {type = "integer", minimum = 1, default = 86400},
@@ -97,7 +122,7 @@ local consumer_schema = {
97122
{
98123
properties = {
99124
algorithm = {
100-
enum = {"HS256", "HS512"},
125+
enum = {"HS256", "HS384", "HS512"},
101126
default = "HS256"
102127
},
103128
},
@@ -106,7 +131,18 @@ local consumer_schema = {
106131
properties = {
107132
public_key = {type = "string"},
108133
algorithm = {
109-
enum = {"RS256", "ES256"},
134+
enum = {
135+
"RS256",
136+
"RS384",
137+
"RS512",
138+
"ES256",
139+
"ES384",
140+
"ES512",
141+
"PS256",
142+
"PS384",
143+
"PS512",
144+
"EdDSA",
145+
},
110146
},
111147
},
112148
required = {"public_key"},
@@ -141,15 +177,24 @@ function _M.check_schema(conf, schema_type)
141177
return false, err
142178
end
143179

180+
local is_hs_alg = conf.algorithm:sub(1, 2) == "HS"
144181
if (conf.algorithm == "HS256" or conf.algorithm == "HS512") and not conf.secret then
145182
return false, "property \"secret\" is required "..
146183
"when \"algorithm\" is \"HS256\" or \"HS512\""
184+
end
185+
186+
if is_hs_alg and not conf.secret then
187+
conf.secret = ngx_encode_base64(resty_random.bytes(32, true))
147188
elseif conf.base64_secret then
148189
if ngx_decode_base64(conf.secret) == nil then
149190
return false, "base64_secret required but the secret is not in base64 format"
150191
end
151192
end
152193

194+
if not is_hs_alg and not conf.public_key then
195+
return false, "missing valid public key"
196+
end
197+
153198
return true
154199
end
155200

@@ -232,15 +277,48 @@ local function get_secret(conf)
232277
return secret
233278
end
234279

235-
local function get_auth_secret(auth_conf)
236-
if not auth_conf.algorithm or auth_conf.algorithm == "HS256"
237-
or auth_conf.algorithm == "HS512" then
238-
return get_secret(auth_conf)
239-
elseif auth_conf.algorithm == "RS256" or auth_conf.algorithm == "ES256" then
240-
return auth_conf.public_key
280+
281+
local function get_real_payload(key, auth_conf, payload)
282+
local real_payload = {
283+
key = key,
284+
exp = ngx_time() + auth_conf.exp
285+
}
286+
if payload then
287+
local extra_payload = core.json.decode(payload)
288+
core.table.merge(real_payload, extra_payload)
289+
end
290+
return real_payload
291+
end
292+
293+
294+
local function get_auth_secret(consumer)
295+
if not consumer.auth_conf.algorithm or consumer.auth_conf.algorithm:sub(1, 2) == "HS" then
296+
return get_secret(consumer.auth_conf)
297+
else
298+
return consumer.auth_conf.public_key
299+
end
300+
end
301+
302+
303+
local function gen_jwt_header(consumer)
304+
local x5c
305+
if consumer.auth_conf.algorithm and consumer.auth_conf.algorithm:sub(1, 2) ~= "HS" then
306+
local public_key = consumer.auth_conf.public_key
307+
if not public_key then
308+
core.log.error("failed to sign jwt, err: missing public key")
309+
core.response.exit(503, "failed to sign jwt")
310+
end
311+
x5c = {public_key}
241312
end
313+
314+
return {
315+
typ = "JWT",
316+
alg = consumer.auth_conf.algorithm,
317+
x5c = x5c
318+
}
242319
end
243320

321+
244322
local function find_consumer(conf, ctx)
245323
-- fetch token and hide credentials if necessary
246324
local jwt_token, err = fetch_jwt_token(conf, ctx)
@@ -249,19 +327,19 @@ local function find_consumer(conf, ctx)
249327
return nil, nil, "Missing JWT token in request"
250328
end
251329

252-
local jwt_obj = jwt:load_jwt(jwt_token)
253-
core.log.info("jwt object: ", core.json.delay_encode(jwt_obj))
254-
if not jwt_obj.valid then
255-
err = "JWT token invalid: " .. jwt_obj.reason
330+
local jwt, err = jwt_parser.new(jwt_token)
331+
if not jwt then
332+
err = "JWT token invalid: " .. err
256333
if auth_utils.is_running_under_multi_auth(ctx) then
257334
return nil, nil, err
258335
end
259336
core.log.warn(err)
260337
return nil, nil, "JWT token invalid"
261338
end
339+
core.log.debug("parsed jwt object: ", core.json.delay_encode(jwt, true))
262340

263341
local key_claim_name = conf.key_claim_name
264-
local user_key = jwt_obj.payload and jwt_obj.payload[key_claim_name]
342+
local user_key = jwt.payload and jwt.payload[key_claim_name]
265343
if not user_key then
266344
return nil, nil, "missing user key in JWT token"
267345
end
@@ -272,7 +350,7 @@ local function find_consumer(conf, ctx)
272350
return nil, nil, "Invalid user key in JWT token"
273351
end
274352

275-
local auth_secret, err = get_auth_secret(consumer.auth_conf)
353+
local auth_secret, err = get_auth_secret(consumer)
276354
if not auth_secret then
277355
err = "failed to retrieve secrets, err: " .. err
278356
if auth_utils.is_running_under_multi_auth(ctx) then
@@ -281,23 +359,24 @@ local function find_consumer(conf, ctx)
281359
core.log.error(err)
282360
return nil, nil, "failed to verify jwt"
283361
end
284-
local claim_specs = jwt:get_default_validation_options(jwt_obj)
285-
claim_specs.lifetime_grace_period = consumer.auth_conf.lifetime_grace_period
286362

287-
jwt_obj = jwt:verify_jwt_obj(auth_secret, jwt_obj, claim_specs)
288-
core.log.info("jwt object: ", core.json.delay_encode(jwt_obj))
363+
-- Now verify the JWT signature
364+
if not jwt:verify_signature(auth_secret) then
365+
core.log.warn("failed to verify jwt: signature mismatch: ", jwt.signature)
366+
return nil, nil, "failed to verify jwt"
367+
end
289368

290-
if not jwt_obj.verified then
291-
err = "failed to verify jwt: " .. jwt_obj.reason
292-
if auth_utils.is_running_under_multi_auth(ctx) then
293-
return nil, nil, err
294-
end
295-
core.log.warn(err)
369+
-- Verify the JWT registered claims
370+
local ok, err = jwt:verify_claims(conf.claims_to_verify, {
371+
lifetime_grace_period = consumer.auth_conf.lifetime_grace_period
372+
})
373+
if not ok then
374+
core.log.error("failed to verify jwt: ", err)
296375
return nil, nil, "failed to verify jwt"
297376
end
298377

299378
if conf.store_in_ctx then
300-
ctx.jwt_auth_payload = jwt_obj.payload
379+
ctx.jwt_auth_payload = jwt.payload
301380
end
302381

303382
return consumer, consumer_conf

0 commit comments

Comments
 (0)