You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/07/25 06:55:28 UTC
[apisix] branch master updated: feat(jwt-auth): support setting clock skew for verifying (#7500)
This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new a2144b5eb feat(jwt-auth): support setting clock skew for verifying (#7500)
a2144b5eb is described below
commit a2144b5ebd9a6a10855d6f01dee40f21913885a3
Author: tzssangglass <tz...@gmail.com>
AuthorDate: Mon Jul 25 14:55:20 2022 +0800
feat(jwt-auth): support setting clock skew for verifying (#7500)
---
apisix/plugins/jwt-auth.lua | 10 +-
docs/en/latest/plugins/jwt-auth.md | 1 +
docs/zh/latest/plugins/jwt-auth.md | 1 +
t/plugin/jwt-auth.t | 4 +-
t/plugin/jwt-auth2.t | 319 +++++++++++++++++++++++++++++++++++++
5 files changed, 332 insertions(+), 3 deletions(-)
diff --git a/apisix/plugins/jwt-auth.lua b/apisix/plugins/jwt-auth.lua
index 82c12c95b..3cd601778 100644
--- a/apisix/plugins/jwt-auth.lua
+++ b/apisix/plugins/jwt-auth.lua
@@ -71,6 +71,11 @@ local consumer_schema = {
vault = {
type = "object",
properties = {}
+ },
+ lifetime_grace_period = {
+ type = "integer",
+ minimum = 0,
+ default = 0
}
},
dependencies = {
@@ -389,7 +394,10 @@ function _M.rewrite(conf, ctx)
core.log.error("failed to retrieve secrets, err: ", err)
return 503, {message = "failed to verify jwt"}
end
- jwt_obj = jwt:verify_jwt_obj(auth_secret, jwt_obj)
+ local claim_specs = jwt:get_default_validation_options(jwt_obj)
+ claim_specs.lifetime_grace_period = consumer.auth_conf.lifetime_grace_period
+
+ jwt_obj = jwt:verify_jwt_obj(auth_secret, jwt_obj, claim_specs)
core.log.info("jwt object: ", core.json.delay_encode(jwt_obj))
if not jwt_obj.verified then
diff --git a/docs/en/latest/plugins/jwt-auth.md b/docs/en/latest/plugins/jwt-auth.md
index f6591a199..2019d1a6b 100644
--- a/docs/en/latest/plugins/jwt-auth.md
+++ b/docs/en/latest/plugins/jwt-auth.md
@@ -49,6 +49,7 @@ For Consumer:
| exp | integer | False | 86400 | [1,...] | Expiry time of the token in seconds. |
| base64_secret | boolean | False | false | | Set to true if the secret is base64 encoded. |
| vault | object | False | | | Set to true to use Vault for storing and retrieving secret (secret for HS256/HS512 or public_key and private_key for RS256). By default, the Vault path is `kv/apisix/consumer/<consumer_name>/jwt-auth`. |
+| 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. |
:::info IMPORTANT
diff --git a/docs/zh/latest/plugins/jwt-auth.md b/docs/zh/latest/plugins/jwt-auth.md
index cefb574cf..b8828ed90 100644
--- a/docs/zh/latest/plugins/jwt-auth.md
+++ b/docs/zh/latest/plugins/jwt-auth.md
@@ -49,6 +49,7 @@ Consumer 端:
| exp | integer | 否 | 86400 | [1,...] | token 的超时时间。 |
| base64_secret | boolean | 否 | false | | 当设置为 `true` 时,密钥为 base64 编码。 |
| vault | object | 否 | | | 是否使用 Vault 作为存储和检索密钥(HS256/HS512 的密钥或 RS256 的公钥和私钥)的方式。该插件默认使用 `kv/apisix/consumer/<consumer name>/jwt-auth` 路径进行密钥检索。 |
+| lifetime_grace_period | integer | 否 | 0 | [0,...] | 定义生成 JWT 的服务器和验证 JWT 的服务器之间的时钟偏移。该值应该是零(0)或一个正整数。 |
:::info IMPORTANT
diff --git a/t/plugin/jwt-auth.t b/t/plugin/jwt-auth.t
index 197a42594..bfee5cf0d 100644
--- a/t/plugin/jwt-auth.t
+++ b/t/plugin/jwt-auth.t
@@ -54,7 +54,7 @@ __DATA__
}
}
--- response_body_like eval
-qr/{"algorithm":"HS256","base64_secret":false,"exp":86400,"key":"123","secret":"[a-zA-Z0-9+\\\/]+={0,2}"}/
+qr/{"algorithm":"HS256","base64_secret":false,"exp":86400,"key":"123","lifetime_grace_period":0,"secret":"[a-zA-Z0-9+\\\/]+={0,2}"}/
@@ -957,7 +957,7 @@ qr/failed to sign jwt/
}
}
--- response_body_like eval
-qr/{"algorithm":"HS512","base64_secret":false,"exp":86400,"key":"123","secret":"[a-zA-Z0-9+\\\/]+={0,2}"}/
+qr/{"algorithm":"HS512","base64_secret":false,"exp":86400,"key":"123","lifetime_grace_period":0,"secret":"[a-zA-Z0-9+\\\/]+={0,2}"}/
diff --git a/t/plugin/jwt-auth2.t b/t/plugin/jwt-auth2.t
index a6c6f31ad..622513387 100644
--- a/t/plugin/jwt-auth2.t
+++ b/t/plugin/jwt-auth2.t
@@ -147,3 +147,322 @@ GET /hello
jwt-header: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTg3OTMxODU0MX0.fNtFJnNmJgzbiYmGB0Yjvm-l6A6M4jRV1l4mnVFSYjs
--- response_body
hello world
+
+
+
+=== TEST 8: use lifetime_grace_period default value
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+
+ -- in order to modify the system_leeway in jwt-validators module
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{ "plugins": {
+ "openid-connect": {
+ "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+ "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+ "discovery": "https://samples.auth0.com/.well-known/openid-configuration",
+ "redirect_uri": "https://iresty.com",
+ "ssl_verify": false,
+ "timeout": 10,
+ "bearer_only": true,
+ "scope": "apisix",
+ "public_key": "-----BEGIN PUBLIC KEY-----\n]] ..
+ [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] ..
+ [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] ..
+ [[-----END PUBLIC KEY-----",
+ "token_signing_alg_values_expected": "RS256"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" ..
+ ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" ..
+ "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" ..
+ "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" ..
+ "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w",
+ }
+ })
+ ngx.status = res.status
+ if res.status >= 300 then
+ ngx.status = res.status
+ ngx.say(res.body)
+ return
+ end
+
+ -- add consumer
+ local code, body, res_data = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "kerouac",
+ "plugins": {
+ "jwt-auth": {
+ "exp": 1,
+ "algorithm": "HS256",
+ "base64_secret": false,
+ "secret": "test-jwt-secret",
+ "key": "test-jwt-a"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- add route
+ code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "jwt-auth": {
+ "query": "jwt",
+ "header": "Mytoken",
+ "cookie": "jwt"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- resgiter jwt sign api
+ local code, body = t('/apisix/admin/routes/2',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "public-api": {}
+ },
+ "uri": "/apisix/plugin/jwt/sign"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- get JWT token
+ local code, err, sign = t('/apisix/plugin/jwt/sign?key=test-jwt-a',
+ ngx.HTTP_GET
+ )
+
+ if code > 200 then
+ ngx.status = code
+ ngx.say(err)
+ return
+ end
+
+ -- verify JWT token
+ local http = require("resty.http")
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri, {headers={Mytoken=sign}})
+
+ -- the JWT has not expired, so it should be valid
+ if res.status >= 300 then
+ ngx.status = res.status
+ ngx.say(res.body)
+ return
+ end
+
+ -- after 1.1 seconds, the JWT should be expired, because the exp is only 1 second
+ ngx.sleep(1.1)
+ res, err = httpc:request_uri(uri, {headers={Mytoken=sign}})
+ ngx.status = res.status
+ ngx.print(res.body)
+ }
+ }
+--- error_code: 401
+--- response_body eval
+qr/failed to verify jwt/
+--- error_log eval
+qr/ailed to verify jwt: 'exp' claim expired at/
+
+
+
+=== TEST 9: lifetime_grace_period is 2 seconds
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+
+ -- in order to modify the system_leeway in jwt-validators module
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{ "plugins": {
+ "openid-connect": {
+ "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+ "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+ "discovery": "https://samples.auth0.com/.well-known/openid-configuration",
+ "redirect_uri": "https://iresty.com",
+ "ssl_verify": false,
+ "timeout": 10,
+ "bearer_only": true,
+ "scope": "apisix",
+ "public_key": "-----BEGIN PUBLIC KEY-----\n]] ..
+ [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] ..
+ [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] ..
+ [[-----END PUBLIC KEY-----",
+ "token_signing_alg_values_expected": "RS256"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" ..
+ ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" ..
+ "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" ..
+ "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" ..
+ "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w",
+ }
+ })
+ ngx.status = res.status
+ if res.status >= 300 then
+ ngx.status = res.status
+ ngx.say(res.body)
+ return
+ end
+
+ -- add consumer
+ local code, body, res_data = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "kerouac",
+ "plugins": {
+ "jwt-auth": {
+ "exp": 1,
+ "algorithm": "HS256",
+ "base64_secret": false,
+ "secret": "test-jwt-secret",
+ "key": "test-jwt-a",
+ "lifetime_grace_period": 2
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- add route
+ code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "jwt-auth": {
+ "query": "jwt",
+ "header": "Mytoken",
+ "cookie": "jwt"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- resgiter jwt sign api
+ local code, body = t('/apisix/admin/routes/2',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "public-api": {}
+ },
+ "uri": "/apisix/plugin/jwt/sign"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ end
+
+ -- get JWT token
+ local code, err, sign = t('/apisix/plugin/jwt/sign?key=test-jwt-a',
+ ngx.HTTP_GET
+ )
+
+ if code > 200 then
+ ngx.status = code
+ ngx.say(err)
+ return
+ end
+
+ -- verify JWT token
+ local http = require("resty.http")
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local httpc = http.new()
+
+ -- after 1.1 seconds, since lifetime_grace_period is 2 seconds,
+ -- so the JWT has not expired, it should be valid
+ ngx.sleep(1.1)
+ local res, err = httpc:request_uri(uri, {headers={Mytoken=sign}})
+ ngx.status = res.status
+ ngx.print(res.body)
+ }
+ }
+--- response_body
+hello world