How to use Kong Oauth2 Plugin

Kong API网关是目前市面上最受欢迎的API网关之一。我在之前的文章中简略介绍过如何使用Traefik来部署Kong和Konga。今天这篇博文是应B站网友的留言,给大家讲讲 Kong Oauth2插件的使用。网络上有太多的文章讲解Oauth2的原理,我的文章风格还是以实际操作为主,尽量把每一步都呈现出来让大家可以照着做,今天的这一片也是如此。

阅读本文前提:

  • 一个安装好的使用数据库的Kong。Oauth2插件不支持Hybrid和DBless模式。
  • 可以使用Kong Admin API,我的范例中Admin API使用的默认的8001端口。
  • 读者需要对Oauth2不同grant有一些基本的了解。
## Kong的准备工作 我今天演示使用的是Kong Gateway (OSS) 2.3.2版本,我会把用到的所有cURL指令和返回的response贴出来供大家参考。

创建Service

创建一个Kong的Service Object指向上游的服务。我会使用httpbin作为上游服务作为演示。

REQUEST:

1
2
3
4
curl -X POST \
--url "http://localhost:8001/services" \
--data "name=oauth2-test" \
--data "url=http://httpbin.org/anything"

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"host": "httpbin.org",
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f",
"protocol": "http",
"read_timeout": 60000,
"tls_verify_depth": null,
"port": 80,
"updated_at": 1615001132,
"ca_certificates": null,
"created_at": 1615001132,
"connect_timeout": 60000,
"write_timeout": 60000,
"name": "oauth2-test",
"retries": 5,
"path": "/anything",
"tls_verify": null,
"tags": null,
"client_certificate": null
}

创建Route

接下来我会创建路径/demo来访问服务。

REQUEST:

1
2
3
curl -X POST \
--url "http://localhost:8001/services/oauth2-test/routes" \
--data 'paths[]=/demo'

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"strip_path": true,
"tags": null,
"updated_at": 1615004204,
"destinations": null,
"headers": null,
"protocols": [
"http",
"https"
],
"methods": null,
"service": {
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f"
},
"snis": null,
"hosts": null,
"name": null,
"path_handling": "v0",
"paths": [
"/demo"
],
"preserve_host": false,
"regex_priority": 0,
"response_buffering": true,
"sources": null,
"id": "e804fef4-fa42-4f7e-be0c-bbe9b9999027",
"https_redirect_status_code": 426,
"request_buffering": true,
"created_at": 1615004204
}

在这之后我们可以通过 curl localhost:8000/demo来访问服务。

启用Oauth2插件

我会在我们的service object上启用这个插件并且自定义 provision_key . 如果你不自定义这个变量的话,kong会自动生成一个。同时我这里启用了全部四种认证方式以做演示,在现实中你只需要启用你需要的grant。

REQUEST:

1
2
3
4
5
6
7
8
9
10
11
12
curl -X POST \
--url http://localhost:8001/services/oauth2-test/plugins/ \
--data "name=oauth2" \
--data "config.scopes[]=email" \
--data "config.scopes[]=phone" \
--data "config.scopes[]=address" \
--data "config.mandatory_scope=true" \
--data "config.provision_key=oauth2-demo-provision-key" \
--data "config.enable_authorization_code=true" \
--data "config.enable_client_credentials=true" \
--data "config.enable_implicit_grant=true" \
--data "config.enable_password_grant=true"

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"created_at": 1615003048,
"id": "8bc8ed59-cdb4-4ac4-ab48-7719655cb9f3",
"tags": null,
"enabled": true,
"protocols": [
"grpc",
"grpcs",
"http",
"https"
],
"name": "oauth2",
"consumer": null,
"service": {
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f"
},
"route": null,
"config": {
"pkce": "lax",
"accept_http_if_already_terminated": false,
"reuse_refresh_token": false,
"token_expiration": 7200,
"mandatory_scope": true,
"enable_client_credentials": true,
"hide_credentials": false,
"enable_authorization_code": true,
"enable_implicit_grant": true,
"global_credentials": false,
"refresh_token_ttl": 1209600,
"enable_password_grant": true,
"scopes": [
"email",
"phone",
"address"
],
"anonymous": null,
"provision_key": "oauth2-demo-provision-key",
"auth_header_name": "authorization"
}
}

我们再次链接 curl localhost:8000/demo 的时候,我们会得到 HTTP/1.1 401 Unauthorized 已经以下错误信息。这就说明Oauth2插件已经成功开启。

1
2
3
4
{
"error": "invalid_request",
"error_description": "The access token is missing"
}

创建Consumer

接下来我们需要创建consumer objectd

REQUEST:

1
2
3
curl -X POST \
--url "http://localhost:8001/consumers/" \
--data "username=oauth2-tester"

RESPONSE:

1
2
3
4
5
6
7
{
"custom_id": null,
"created_at": 1615003502,
"id": "06d53376-8bfd-4bc7-aaaf-05c37316e7ef",
"tags": null,
"username": "oauth2-tester"
}

创建Oauth2 Credential (App)

然后我们会在这个consumer下创建Oauth2的身份凭证。在这里我也会使用自定义的 client_idclient_secret。如果留空,Kong会自动生成这两个变量。

如果使用Kong来生成身份凭证请切记不要添加 hash_secret=true 在您的curl命令里面。

REQUEST

1
2
3
4
5
6
7
curl -X POST \
--url "http://localhost:8001/consumers/oauth2-tester/oauth2/" \
--data "name=Oauth2 Demo App" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "redirect_uris[]=http://localhost:8000/demo" \
--data "hash_secret=true"

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"created_at": 1615004674,
"id": "f602f09b-7b7e-4326-b236-2fa8d45badff",
"tags": null,
"name": "Oauth2 Demo App",
"client_secret": "$pbkdf2-sha512$i=10000,l=32$e3SNVIWRFt8PuBxjoL1ncQ$/hF26HS30QHopDLMzlZqC+zv0nt3m4YFokuW9eTma6Q",
"client_id": "oauth2-demo-client-id",
"redirect_uris": [
"http://localhost:8000/demo"
],
"hash_secret": true,
"client_type": "confidential",
"consumer": {
"id": "06d53376-8bfd-4bc7-aaaf-05c37316e7ef"
}
}

接下来我们就可以测试不同的grant了。

Auth with grants

Authorization Code

我们需要先发送认证的请求到https://localhost:8443/demo/oauth2/authorize 获取一个认证码。然后再使用认证码到https://localhost:8443/demo/oauth2/token请求一个通行码。(我这里就是瞎翻的authorized code和access code。他们也许有正确的中文翻译,见谅)

Request Authorized Code

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=code" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo?code=jvnD1XBgFqZuqT2OlbcXpDiOlFkx75bU"
}

这样我们就获取到我们的认证码 jvnD1XBgFqZuqT2OlbcXpDiOlFkx75bU,我们可以通过它来请求通行码。

Request Access Token

REQUEST:

1
2
3
4
5
6
7
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=authorization_code" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "code=oyPC89DOjHNNc7BBV9YWuUxJQDd5M1TU" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "LqJW6mVH4XsNZnoQ5fYbjngBsbUJPVPh",
"token_type": "bearer",
"access_token": "BZiZzJVEuP2mgNvZZBr0mgbRtKsdqgZf",
"expires_in": 7200
}

Implicit

因为安全性的问题,这个可能是你不应该使用的一个grant。更多的原因可以参考Okta的这篇 文章。按照我的理解Implicit grant应该只用来做身份的认证而不应该返回通行码用来使用API。使用这个grant的话只需要发送client_id 到认证服务器https://localhost:8443/demo/oauth2/authorize就能获取到通行码。

Request Access Token

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=token" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo#access_token=Ubs61rbN0JSO6JMV9N7WC4ZvCWEpWp5z&expires_in=7200&token_type=bearer"
}

你可以在返回的uri的fragment里面找到access_token

Client Credentials

这个grant主要应用在机器对机器之间,因此认证服务器只会返回通行码而不会返回刷新码。每次通行码过期之后都需要重新请求新的。

Request Access Code

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=client_credentials" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "provision_key=oauth2-demo-provision-key" \
--insecure

RESPONSE:

1
2
3
4
5
{
"token_type": "bearer",
"access_token": "7mKwrytPCZEgTjbr40rV5L0dk2Zykota",
"expires_in": 7200
}

Password

该flow需要用户提供认证的用户名,因此用户需要在Kong的前面添加用户身份认证并且提供authenticated_userid给Kong来颁发通行码和刷新码给用户。

Request Access Code

REQUEST:

1
2
3
4
5
6
7
8
9
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=password" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "rYokjg6H8Vi23xcdLKJhGBbt4OkbNTpy",
"token_type": "bearer",
"access_token": "2TVKDlWyoFODlNuIvQaLdCFU7Ids8Gyk",
"expires_in": 7200
}

使用 access_token

当我们获取到 access_token 之后我们就可以再次访问我们需要使用的服务了。

1
2
3
curl -X GET \
--url "http://localhost:8000/demo" \
--header "Authorization: Bearer <ACCESS_TOKEN>"

Several more things

使用PKCE

在使用authorization code grant的时候,用户可以使用PKCE加强安全性。

生成Verifier and Challenge

现实中用户需要自己想办法在自己的程序中按需自动生成这两个变量。在我们的演示中,我会使用https://tonyxu-io.github.io/pkce-generator/来生成。

1
2
3
4
5
Code Verifier:
8FK~B.3ERQPsn4xoSo.7pkmxc6wEiFabpqooHnFJKyyT3ZI41jh9DML0TA7UTVTYrxhUtsNfcOp9RcVhyKR~2GdWCFlv00WKFJ1ha_acuzeuyFYDI1.j4nJ3epQUmc0w

Code Challenge:
nVFqpBvGXtATi0hhNnNuWE5PZNRQTNGR95DJZNcXEaU

Request Authorized Code

REQUEST:

1
2
3
4
5
6
7
8
9
10
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=code" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--data "code_challenge=nVFqpBvGXtATi0hhNnNuWE5PZNRQTNGR95DJZNcXEaU" \
--data "code_challenge_method=S256" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo?code=5CzRTGquWIq7ePVjuX0Yyx5XZsCZUlML"
}

Request Access Token

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=authorization_code" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "code_verifier=8FK~B.3ERQPsn4xoSo.7pkmxc6wEiFabpqooHnFJKyyT3ZI41jh9DML0TA7UTVTYrxhUtsNfcOp9RcVhyKR~2GdWCFlv00WKFJ1ha_acuzeuyFYDI1.j4nJ3epQUmc0w" \
--data "code=51yMXT93QTQUn0zqbvBsUsWNNRRT3sce" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "c3blcIo9QwexfYAGne98u91vKBd3W9pW",
"token_type": "bearer",
"access_token": "pCMgPx97ApLJFM9zIv6TmdXEWmH5xlLq",
"expires_in": 7200
}

Refresh Token

在上述的例子中,各位可以看到 authorization_codepassword grant 会返回一个refresh_token给用户。用户可以使用这个刷新码来请求信的通行码。Kong默认每个刷新码只能使用一次。

REQUEST:

1
2
3
4
5
6
7
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=refresh_token" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "refresh_token=<REFRESH_TOKEN>" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "AbvtChlNibVCyQxmf0Ue0lQ9fzXLCNQv",
"token_type": "bearer",
"access_token": "d3n0lHkrvYi6CDolsDWdMs0lZ8PblFyQ",
"expires_in": 7200
}

Docker Image

我也制作了一个简陋的php程序来通过这四种Grant来获取access token。你可以在https://hub.docker.com/r/fomm/kong-oauth2-demo`下载到这个image。只需要

1
docker run -d -p 8080:80 fomm/kong-oauth2-demo

之后就可以在localhost:8080打开这个小程序了。

以上就是我今天想要讲的全部内容。如果你有关于Kong的一些问题,插件的使用,部署的方式等等想让我讲的话,请您到我的Bilibili或者油管留言。

谢谢