Skip to content

Commit 528ec89

Browse files
feat(jwt-auth): support configuring key_claim_name (#11772)
1 parent a0b1a45 commit 528ec89

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

apisix/plugins/jwt-auth.lua

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ local schema = {
4848
hide_credentials = {
4949
type = "boolean",
5050
default = false
51-
}
51+
},
52+
key_claim_name = {
53+
type = "string",
54+
default = "key",
55+
minLength = 1,
56+
},
5257
},
5358
}
5459

@@ -240,7 +245,8 @@ function _M.rewrite(conf, ctx)
240245
return 401, {message = "JWT token invalid"}
241246
end
242247

243-
local user_key = jwt_obj.payload and jwt_obj.payload.key
248+
local key_claim_name = conf.key_claim_name
249+
local user_key = jwt_obj.payload and jwt_obj.payload[key_claim_name]
244250
if not user_key then
245251
return 401, {message = "missing user key in JWT token"}
246252
end

ci/common.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ create_lua_deps() {
3333
echo "Create lua deps"
3434

3535
make deps
36+
37+
# just for jwt-auth test
38+
luarocks install lua-resty-openssl --tree deps
39+
3640
# maybe reopen this feature later
3741
# luarocks install luacov-coveralls --tree=deps --local > build.log 2>&1 || (cat build.log && exit 1)
3842
# for github action cache

docs/en/latest/plugins/jwt-auth.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ For Consumer:
4747
| exp | integer | False | 86400 | [1,...] | Expiry time of the token in seconds. |
4848
| base64_secret | boolean | False | false | | Set to true if the secret is base64 encoded. |
4949
| lifetime_grace_period | integer | False | 0 | [0,...] | Define the leeway in seconds to account for clock skew between the server that generated the jwt and the server validating it. Value should be zero (0) or a positive integer. |
50+
| key_claim_name | string | False | key | | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). |
5051

5152
NOTE: `encrypt_fields = {"secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
5253

t/plugin/jwt-auth4.t

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
use t::APISIX 'no_plan';
19+
20+
repeat_each(1);
21+
no_long_string();
22+
no_root_location();
23+
no_shuffle();
24+
25+
add_block_preprocessor(sub {
26+
my ($block) = @_;
27+
28+
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
29+
$block->set_value("no_error_log", "[error]");
30+
}
31+
32+
if (!defined $block->request) {
33+
$block->set_value("request", "GET /t");
34+
if (!$block->response_body) {
35+
$block->set_value("response_body", "passed\n");
36+
}
37+
}
38+
});
39+
40+
run_tests;
41+
42+
__DATA__
43+
44+
=== TEST 1: add consumer with username and plugins
45+
--- config
46+
location /t {
47+
content_by_lua_block {
48+
local t = require("lib.test_admin").test
49+
local code, body = t('/apisix/admin/consumers',
50+
ngx.HTTP_PUT,
51+
[[{
52+
"username": "jack",
53+
"plugins": {
54+
"jwt-auth": {
55+
"key": "user-key",
56+
"secret": "my-secret-key"
57+
}
58+
}
59+
}]]
60+
)
61+
62+
if code >= 300 then
63+
ngx.status = code
64+
end
65+
ngx.say(body)
66+
}
67+
}
68+
--- response_body
69+
passed
70+
71+
72+
73+
=== TEST 2: enable jwt auth plugin using admin api
74+
--- config
75+
location /t {
76+
content_by_lua_block {
77+
local t = require("lib.test_admin").test
78+
local code, body = t('/apisix/admin/routes/1',
79+
ngx.HTTP_PUT,
80+
[[{
81+
"plugins": {
82+
"jwt-auth": {
83+
"key": "user-key",
84+
"secret": "my-secret-key",
85+
"key_claim_name": "iss"
86+
}
87+
},
88+
"upstream": {
89+
"nodes": {
90+
"127.0.0.1:1980": 1
91+
},
92+
"type": "roundrobin"
93+
},
94+
"uri": "/hello"
95+
}]]
96+
)
97+
98+
if code >= 300 then
99+
ngx.status = code
100+
end
101+
ngx.say(body)
102+
}
103+
}
104+
--- response_body
105+
passed
106+
107+
108+
109+
=== TEST 3: verify (in header)
110+
--- config
111+
location /t {
112+
content_by_lua_block {
113+
local function gen_token(payload)
114+
local buffer = require "string.buffer"
115+
local openssl_mac = require "resty.openssl.mac"
116+
117+
local base64 = require "ngx.base64"
118+
local base64_encode = base64.encode_base64url
119+
120+
local json = require("cjson")
121+
122+
local function sign(data, key)
123+
return openssl_mac.new(key, "HMAC", nil, "sha256"):final(data)
124+
end
125+
local header = { typ = "JWT", alg = "HS256" }
126+
local buf = buffer.new()
127+
128+
buf:put(base64_encode(json.encode(header))):put("."):put(base64_encode(json.encode(payload)))
129+
130+
local ok, signature = pcall(sign, buf:tostring(), "my-secret-key")
131+
if not ok then
132+
return nil, signature
133+
end
134+
135+
buf:put("."):put(base64_encode(signature))
136+
137+
return buf:get()
138+
end
139+
140+
local payload = {
141+
sub = "1234567890",
142+
iss = "user-key",
143+
exp = 9916239022
144+
}
145+
146+
local token = gen_token(payload)
147+
148+
local http = require("resty.http")
149+
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
150+
local opt = {method = "POST", headers = {["Authorization"] = "Bearer " .. token}}
151+
local httpc = http.new()
152+
local res = httpc:request_uri(uri, opt)
153+
assert(res.status == 200)
154+
155+
ngx.print(res.body)
156+
}
157+
}
158+
--- request
159+
GET /t
160+
--- more_headers
161+
--- response_body
162+
hello world

0 commit comments

Comments
 (0)