api 网关功能
1.功能描述
支持用户角色维护,OpenAPI导入,授权关系维护,应用部署,后端服务注册,主动健康检查配置,登录及授权校验
- 用户维护API
需要提供对用户,组及角色及相互关系维护的API
一个用户可以对应多个用户组
角色将赋予用户组
- 登录API
API设计时,需要考虑不同的后端登录服务类型
需要提供一个内置的登录服务,内置的登录服务提供如下登录方式:a.)持久化存储中保存的用户名密码做登录校验 b.)通过Email 发送校验码,用户通过校验码登录。
客户端调用登录API后,将获得一个 token, token 只是一个uuid, 其它的信息(expire_time, role, …) 保存到持久化存储中。
后续对其它API的调用需要在Header 中设置该token。
- 授权API
需要提供 location 下api与角色间授权关系维护的API
HTTP 不同的method 需要能够设置不同的访问权限
- 访问校验
对应用提供的API 访问,API网关需要校验token 有效性及授权关系。
Token 无效或过期返回 401错误。
对应角色无访问权限返回403 错误。
校验成功则由应用进行后续处理。
- 应用部署
文件上传使用已有的 /api/v1/upload 接口,做为应用服务器,提供应用部署接口, 需要能够将上传的压缩包解压,部署接口中需要调用动态location , 入口文件可以使用预先约定的方式
- API Gateway 管理员agw_admin 的默认密码,rpm/deb 安装时,将生成默认密码,使用 sbin/agw_passwd 工具进行密码设置
- API Gateway 不允许删除默认的用户
2.依赖模块
njet.conf:
load_module modules/njt_http_token_sync_module.so;
njet_ctrl.conf:
load_module modules/njt_http_lua_module.so;
3.配置样例
njet.conf
helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/njet_ctrl.conf;
helper rsync modules/njt_helper_rsync_module.so conf/rsync.conf;
load_module modules/njt_http_dyn_map_module.so;
load_module modules/njt_agent_dynlog_module.so;
load_module modules/njt_http_location_module.so;
load_module modules/njt_dyn_ssl_module.so;
load_module modules/njt_http_vtsc_module.so;
load_module modules/njt_http_dyn_limit_module.so;
load_module modules/njt_http_dyn_crl_module.so;
load_module modules/njt_http_lua_module.so;
load_module modules/njt_http_dyn_lua_module.so;
load_module modules/njt_http_token_sync_module.so; ## 依赖该模块
cluster_name helper;
node_name node156;
worker_processes 1;
#user root;
error_log logs/error.log info;
pid logs/njet.pid;
events {
worker_connections 1024;
}
http {
resolver 8.8.8.8;
include mime.types;
access_log off;
vhost_traffic_status_zone;
lua_package_path "$prefix/lualib/lib/?.lua;$prefix/modules/?.lua;/usr/local/njet/modules/?.lua;$prefix/apps/?.lua;;";
lua_package_cpath "$prefix/lualib/clib/?.so;;";
token_sync zone=token:4M sync_time=5s clean_time=10s;
lua_shared_dict configuration_data 20M;
init_by_lua_block {
local api_gateway=require("api_gateway")
api_gateway.init_master()
}
init_worker_by_lua_block {
local api_gateway=require("api_gateway")
api_gateway.init_worker()
}
upstream upstream_balancer {
server 0.0.0.1;
balancer_by_lua_block {
balancer.balance()
}
keepalive 320;
keepalive_time 1h;
keepalive_timeout 120s;
keepalive_requests 10000;
}
server {
listen 8080;
server_name frontserver8080;
location / {
root html;
}
location /api_gateway {
# content_by_lua_file modules/api_gateway/auth.lua;
content_by_lua_block {
local api_gateway=require("api_gateway")
api_gateway.auth()
}
}
location /token_get {
content_by_lua_block {
local token_lib=require("njt.token")
local args, err = njt.req.get_uri_args()
local key = args["key"]
local rc,msg = token_lib.token_get(key)
if rc == 0 then
njt.say("old value is: "..msg)
else
njt.say("there is no such key in kv")
end
}
}
}
server {
listen 8001;
server_name backserver8001;
location / {
return 200 "80018001";
}
}
server {
listen 8002;
server_name backserver8002;
location / {
return 200 "80028002";
}
}
server {
listen 8003;
server_name backserver8003;
location / {
return 200 "80038003";
}
}
}
stream {
server {
listen 238.255.253.251:5557 udp;
gossip zone=test:1m heartbeat_timeout=100ms nodeclean_timeout=1s local_ip=192.168.40.156 sync_port=8873 ctrl_port=8081 ;
}
}
njet_ctrl.conf:
load_module modules/njt_http_sendmsg_module.so;
#load_module modules/njt_helper_broker_module;
load_module modules/njt_ctrl_config_api_module.so;
load_module modules/njt_helper_health_check_module.so;
load_module modules/njt_http_upstream_api_module.so;
load_module modules/njt_http_location_api_module.so;
load_module modules/njt_doc_module.so;
load_module modules/njt_http_vtsd_module.so;
load_module modules/njt_http_upload_module.so;
load_module modules/njt_http_crl_api_module.so;
load_module modules/njt_http_lua_module.so;
cluster_name helper;
node_name node1;
error_log logs/error_ctrl.log info;
events {
worker_connections 1024;
}
http {
dyn_sendmsg_conf conf/iot-ctrl.conf;
config_req_pool_size 1000;
access_log logs/access_ctrl.log combined;
include mime.types;
resolver 8.8.8.8;
lua_package_path "$prefix/lualib/lib/?.lua;$prefix/modules/?.lua;/usr/local/njet/modules/?.lua;;";
lua_package_cpath "$prefix/lualib/clib/?.so;;";
init_by_lua_block {
local _=require("lor.index")
local _=require("lsqlite3complete")
}
server {
listen 8081;
client_max_body_size 10G;
location /api {
dyn_module_api;
}
location /doc {
doc_api;
}
location /api_gateway {
access_by_lua_block {
local ac=require("api_gateway.access.control")
local access=ac.new("/api_gateway")
access:check()
}
content_by_lua_block {
local api_gateway=require("api_gateway")
api_gateway.main()
}
}
location / {
root /usr/local/njet/html;
index upload.html;
}
location /metrics {
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
}
}
}
rsync.conf:
{
"log_file": "logs/rsync.log",
"log_level": "debug",
"watch_dirs": [
{
"identifier": "rsync1",
"prefix": "/home/limin/api_gateway",
"dir": "/home/limin/api_gateway/apigw_data"
},
{
"identifier": "rsync5",
"prefix": "/home/limin/api_gateway/",
"dir": "/home/limin/api_gateway/apps"
}
]
}
ctrl_kv.conf:
topic /gossip/#
4.调用样例
使用 curl 测试 各表操作的API 时, 关闭了校验,开启校验时,需要使用 login 获取token 后,并在header 中设置 bearer token。
使用命令行工具重置agw_admin初始密码
#初始获取TOKEN,因为还没有设置密码,密码是随机生成的,用户不知道这个密码,所以返回can't get user
[njet@k8s-master158 njet]$ curl -X 'POST' 'http://192.168.40.158:8812/api_gateway/auth/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{
"api_group_name": "app",
"login_type": "internal",
"login_data": {
"username": "agw_admin",
"password": "Xptykm;1qrdv"
}
}'
{"code":20,"msg":"can't get user"}
#为超级用户设置密码
[njet@k8s-master158 njet]$ /usr/local/njet/sbin/agw_passwd
Usage: /usr/local/njet/sbin/agw_passwd <database_path> [password]
[njet@k8s-master158 njet]$ sudo /usr/local/njet/sbin/agw_passwd /usr/local/njet/apigw_data/api_gateway.db 123456
Password hash updated successfully for user_id=1, password: 123456
#使用登录的密码获取token
[njet@k8s-master158 njet]$ curl -X 'POST' 'http://192.168.40.158:8812/api_gateway/auth/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{
"api_group_name": "app",
"login_type": "internal",
"login_data": {
"username": "agw_admin",
"password": "123456"
}
}'
{"msg":"success","code":0,"token":"4150e7b2-5907-4fbd-8c4c-cdd69f75cd37"}
Login API
使用内置登录服务获取 token
curl -X POST -d '{"login_data":{"username": "agw_admin", "password": "123456"}}' localhost:8081/api_gateway/auth/login
{"code":0,"msg":"success","token":"8f61f744-3d86-46c4-a460-b2c7d9a44f9e"}
使用 Email 方式登录
curl -X POST -d '{"email":"hongxincn@139.com"}' localhost:8081/api_gateway/auth/verification
curl -X POST -d '{"login_data":{"email": "hongxincn@139.com", "verification_code": "E9zOrH"}}' localhost:8081/api_gateway/auth/login
{"msg":"success","token":"1e0eedca-96a7-401e-84db-1ab190f0505a","code":0}
curl -H "Authorization: bearer 1e0eedca-96a7-401e-84db-1ab190f0505a" localhost:8081/api_gateway/identities/users/1
{"msg":"success","data":{"mobile":"","id":1,"email":"13696964249@139.com","name":"aa"},"code":0}
修改email:
curl -H "Authorization: bearer 83e417a9-1072-4e89-8372-5305498c23df" -X PUT -d '{ "email":"hongxina@tmlake.com"}' localhost:8081/api_gateway/identities/users/1
{"msg":"success","code":0}
curl -X POST -d '{"email":"hongxina@tmlake.com"}' localhost:8081/api_gateway/auth/verification{"msg":"success","code":0}
表API_USER
使用POST 方法添加user,user 添加后的id 会返回给调用方
curl -X POST -d '{"domain":"app1", "name":"firstacc", "email":"firstacc@test.com", "password":"dd"}' localhost:8081/api_gateway/identities/users
{"code":0,"data":{"id":10},"msg":"success"}
使用GET方法查询,支持通过 id 或name 进行查询
curl localhost:8081/api_gateway/identities/users/10
{"code":0,"msg":"success","data":{"name":"firstacc@app1","email":"firstacc@test.com","mobile":""}}
curl localhost:8081/api_gateway/identities/users/name/firstacc
{"code":0,"msg":"success","data":{"email":"firstacc@test.com","name":"firstacc","mobile":""}}
使用PUT方法进行修改,需要通过id 进行修改。
curl localhost:8081/api_gateway/identities/users/12
{"msg":"success","data":{"email":"firstacc3@test.comd","id":12,"mobile":"","name":"firstacc3d"},"code":0}
curl -X PUT -d '{"name":"updatedname", "email":"updated@test.comd", "password":"uppass"}' localhost:8081/api_gateway/identities/users/12
{"code":0,"msg":"success"}
curl localhost:8081/api_gateway/identities/users/12
{"data":{"mobile":"","name":"updatedname","email":"updated@test.comd","id":12},"code":0,"msg":"success"}
使用 DELETE 方法进行删除
curl -X DELETE localhost:8081/api_gateway/identities/users/10
{"msg":"success","code":0}
表API_USER_GROUP
使用POST 方法添加group,group 添加后的id 会返回给调用方
curl -X POST -d '{"name":"firstacc"}' localhost:8081/api_gateway/identities/groups
{"msg":"success","data":{"id":2},"code":0}
使用GET方法查询,支持通过 id 或name 进行查询
curl localhost:8081/api_gateway/identities/groups/2
{"code":0,"msg":"success","data":{"desc":"","name":"firstacc","id":2}}
curl localhost:8081/api_gateway/identities/groups/name/firstacc
{"code":0,"msg":"success","data":{"desc":"","name":"firstacc","id":2}}
使用PUT方法进行修改,需要通过id 进行修改。
curl localhost:8081/api_gateway/identities/groups/2
{"code":0,"data":{"name":"firstacc","desc":"","id":2},"msg":"success"}
curl -X PUT -d '{"name":"updatedGroupname", "desc": "updated desc"}' localhost:8081/api_gateway/identities/groups/2
{"code":0,"msg":"success"}
curl localhost:8081/api_gateway/identities/groups/2
{"code":0,"msg":"success","data":{"name":"updatedGroupname","desc":"updated desc","id":2}}
使用 DELETE 方法进行删除
curl -X DELETE localhost:8081/api_gateway/identities/groups/2
{"msg":"success","code":0}
表API_USER_GROUP_REL
使用PUT 更新用户及组的关系,将使用提交的报文覆盖掉现有的配置, PUT 一个 groups 的空数组,对应用户的记录将被删除
curl -X PUT -d '{"groups": [2, 12]}' localhost:8081/api_gateway/identities/users/2/groups
使用 GET 查询当前的配置, 目前返回结果只包含 group id 的列表
curl localhost:8081/api_gateway/identities/users/2/groups
{"data":{"groups":[2,12]},"code":0,"msg":"success"}
表 API_ROLE
使用POST 方法添加role,role添加后的id 会返回给调用方(
curl -X POST -d '{"name":"role_a"}' localhost:8081/api_gateway/identities/roles
{"msg":"success","data":{"id":2},"code":0}
使用GET方法查询,支持通过 id 或name 进行查询
curl localhost:8081/api_gateway/identities/roles/2
{"code":0,"msg":"success","data":{"desc":"","name":"role_a","id":2}}
curl localhost:8081/api_gateway/identities/roles/name/role_a
{"data":{"name":"role_a","desc":"","id":2},"code":0,"msg":"success"}
使用PUT方法进行修改,需要通过id 进行修改。
curl -X PUT -d '{"name":"updateRole", "desc": "updated desc"}' localhost:8081/api_gateway/identities/roles/2
{"code":0,"msg":"success"}
使用 DELETE 方法进行删除
curl -X DELETE localhost:8081/api_gateway/identities/roles/2
{"msg":"success","code":0}
表 API_USER_GROUP_ROLE_REL
使用PUT 更新用户及组的关系,将使用提交的报文覆盖掉现有的配置, PUT 一个 roles 的空数组,对应group的记录将被删除
curl -X PUT -d '{"roles": [2, 12]}' localhost:8081/api_gateway/identities/groups/2/roles
使用 GET 查询当前的配置, 目前返回结果只包含 role id 的列表
curl localhost:8081/api_gateway/identities/groups/2/roles
{"data":{"roles":[2,12]},"code":0,"msg":"success"}
表 API_GROUP
使用POST 方法添加role,role添加后的id 会返回给调用方
注意:添加用户时用的domain 是需要使用 api group的 domain,这样默认添加的 domain 才会一致, 否则需要手动输入user 的domain 信息
curl -X POST -d '{"name":"app2", "base_path": "/app2","desc": "新增api group2",
"domain": "app2"}' localhost:8081/api_gateway/entities/api_groups
{"msg":"success","data":{"id":2},"code":0}
使用GET方法查询,支持通过 id 或name 进行查询
curl http://localhost:8081/api_gateway/entities/api_groups/2
{"data":{"desc":"","base_path":"\/app2","id":1,"name":"app2"},"code":0,"msg":"success"}
curl http://localhost:8081/api_gateway/entities/api_groups/name/app2
{"data":{"desc":"","base_path":"\/app2","id":1,"name":"app1"},"code":0,"msg":"success"}
使用PUT方法进行修改,需要通过id 进行修改。
curl -X PUT -d '{"name":"app2", "base_path": "/newapp2", "desc":"app1 group"}' http://localhost:8081/api_gateway/entities/api_groups/2
{"code":0,"msg":"success"}
使用 DELETE 方法进行删除
curl -X DELETE http://localhost:8081/api_gateway/entities/api_groups/2
{"msg":"success","code":0}
POST 一个 oapi3.0 json 导入 api
curl -X POST -d @petstore.json localhost:8081/api_gateway/entities/api_groups/2/oas3
{"code":0,"msg":"success"}
GET 获取API Group下的所有API
curl localhost:8081/api_gateway/entities/api_groups/2/apis
{"msg":"success","data":[{"sign_validation":0,"id":10,"desc":"List all pets","path":"\/pets","method":"get","name":"Swagger Petstore","group_id":2,"param_mode":0},{"sign_validation":0,"id":11,"desc":"Create a pet","path":"\/pets","method":"post","name":"Swagger Petstore","group_id":2,"param_mode":0},{"sign_validation":0,"id":12,"desc":"Info for a specific pet","path":"\/pets\/{petId}","method":"get","name":"Swagger Petstore","group_id":2,"param_mode":0}],"code":0}
表 API_GRANT_RBAC
通过 role_id 更新对应role 授权的 API 列表
curl -X PUT -d '{"apis": [1, 2]}' localhost:8081/api_gateway/identities/roles/1/apis
通过 role_id 查询对应role 授权的 API 列表
curl localhost:8081/api_gateway/identities/roles/1/apis
{"code":0,"msg":"success","data":{"apis":[1,2]}}
添加应用
首先使用 upload 功能上传应用压缩包。
使用 /api_gateway/deploy/app 部署上传的应用, 该api 中会调用njet 的动态添加location
curl -X 'POST' 'http://localhost:8081/api_gateway/deploy/app' -d '{ "uploaded_file": "5a655cd81aa8b6d477d8aeebd8f76ba7.dat", "app_type": "lua", "base_path": "/app2"}'
添加的应用,如果需要使用API 网关提供的 rbac 校验功能, 可以用动态lua 配置添加 location 的校验规则
curl --location --request PUT 'http://localhost:8081/api/v1/config/http_lua' \
--header 'Content-Type: application/json' \
--data '{
"servers": [
{
"listens": [
"0.0.0.0:8080"
],
"serverNames": [
""
],
"locations": [
{
"location": "/app2",
"lua": {
"access_by":"local ac=require(\"api_gateway.access.control\")\nlocal access=ac.new(\"/app2\") \naccess:check()\n"
}
}
]
}
]
}'
使用DELETE 删除应用
curl -X 'DELETE' 'http://localhost:8081/api_gateway/deploy/app' -d '{ "base_path": "/app2"}'
注册后端服务
njet 主配置可以参考 conf/njet.conf.api_gateway.example,
使用 POST 注册一个后端服务, 注册服务时,将自动添加location 及upstream
curl -X 'POST' \
'http://localhost:8081/api_gateway/conf/service' \
-d '{
"base_path": "/servapp1",
"upstream": {
"endpoints":[{"address":"127.0.0.1","port":"1234"}]
}
}'
{"code":0,"msg":"success"}
使用PUT 更新已有的后端服务的 upstream
curl -X 'PUT' \
'http://localhost:8081/api_gateway/conf/service' \
-d '{
"base_path": "/servapp1",
"upstream": {
"endpoints":[{"address":"127.0.0.1","port":"2345"}],
"healthCheck":{"interval":10,"path":"/health", "fails":5, "passes":5, "statusMatch":"200"}
}
}'
使用DELETE 删除一个后端服务
curl -X 'DELETE' \
'http://localhost:8081/api_gateway/conf/service' \
-d '{
"base_path": "/servapp1"
}'
/conf/upstreams 查询配置的upstreams
curl http://localhost:8081/api_gateway/conf/upstreams
{"msg":"success","data":[{"load-balance":"round_robin","name":"up_servapp1","endpoints":[{"port":"1234","address":"127.0.0.1"}]}],"code":0}
授权校验
使用非法的 auth token
curl -H "Authorization: bearer aa" localhost:8080/app2/pets -v
* Trying ::1:8080...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /app2/pets HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Authorization: bearer aa
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Server: njet/2.1.0
< Date: Thu, 25 Apr 2024 06:45:22 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
{"code":20,"msg":"token is not valid"}
* Connection #0 to host localhost left intact
对应的Role 无权限访问
curl -H "Authorization: bearer 939cd52f-17aa-4657-ad17-e938a153b406" localhost:8080/app2/pets -v
* Trying ::1:8080...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /app2/pets HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Authorization: bearer 939cd52f-17aa-4657-ad17-e938a153b406
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Server: njet/2.1.0
< Date: Thu, 25 Apr 2024 07:31:14 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
{"code":30,"msg":"API access is not allowed"}
* Connection #0 to host localhost left intact
使用正确的 token, 且role 有权限
curl -X POST -d '{"login_data":{"username": "aa", "password": "aa"}}' localhost:8081/api_gateway/auth/login
{"code":0,"msg":"success","token":"83584d03-86c8-45e9-9f22-347c3c3bd278"}
curl -H "Authorization: bearer 83584d03-86c8-45e9-9f22-347c3c3bd278" localhost:8080/app2/pets
app2 get pets
集群内token 同步
156上:
curl -X 'POST' 'localhost:8081/api_gateway/auth/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{
"api_group_name": "app",
"login_type": "internal",
"login_data": {
"username": "agw_admin",
"password": "Xptykm;1qrdv"
}
}'
{"code":0,"token":"02f4b081-8efa-4087-87c1-1339cdecf3a0","msg":"success"}
使用156 上login 登录获取的token在145 上使用:
curl -X 'GET' \
'http://192.168.40.145:8081/api_gateway/identities/users/2' \
-H 'accept: application/json' \
-H 'Authorization: Bearer 02f4b081-8efa-4087-87c1-1339cdecf3a0'
{
"code": 0,
"msg": "success",
"data": {
"mobile": "12345674539",
"name": "njet154@app154",
"email": "limins154@tmlake.com",
"id": 2
}
}
/conf/sysconfig 接口
#获取token
curl -X 'POST' 'localhost:8812/api_gateway/auth/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{
"api_group_name": "app",
"login_type": "internal",
"login_data": {
"username": "agw_admin",
"password": "Xptykm;1qrdv"
}
}'
{"msg":"success","token":"c35157d0-cca1-4e00-b11f-e67d069a2299","code":0}
#修改api gateway 配置
curl -X 'POST' \
'localhost:8812/api_gateway/conf/sysconfig' \
-H 'accept: application/json' \
-H 'Authorization: Bearer c35157d0-cca1-4e00-b11f-e67d069a2299' \
-H 'Content-Type: application/json' \
-d '[
{
"config_key": "ctrl_api_base",
"config_value": "http://127.0.0.1:8812/api/v1",
"config_type": "string"
}
]'
{
"msg": "success",
"code": 0,
"data": ""
}
#查询api gateway 配置
curl -X 'GET' 'localhost:8812/api_gateway/conf/sysconfig/ctrl_api_base' -H 'accept: application/json' -H 'Authorization: Bearer c35157d0-cca1-4e00-b11f-e67d069a2299' |jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 136 0 136 0 0 36179 0 --:--:-- --:--:-- --:--:-- 45333
{
"msg": "success",
"code": 0,
"data": [
{
"config_key": "ctrl_api_base",
"config_type": "string",
"config_value": "http://127.0.0.1:8081/api/v1"
}
]
}
API Group 添加用户id 的校验
导入oas3格式文档改为使用name
API Group 中包含 domain
添加含domain 的api group
curl -X 'POST' \
'http://192.168.40.158:8081/api_gateway/entities/api_groups' \
-H 'accept: application/json' \
-H 'Authorization: Bearer 316a2a23-b576-452a-b25d-c3b32f14c45d' \
-H 'Content-Type: application/json' \
-d '{
"name": "api_group2",
"base_path": "/app2",
"desc": "新增api group2",
"domain": "app2"
}'
返回:
{
"msg": "success",
"code": 0,
"data": {
"id": 5
}
}
通过userid及密码方式进行校验:
curl 'localhost:8080/app2/pet/1?userId=njet2&pwd=123456'
in /app2 main.lua
curl 'localhost:8080/app2/pet/1?userId=njet3&pwd=123456'
{"msg":"can't get user using parameters: njet3@app2 / 123456","code":30}
curl 'localhost:8080/app2/pet/1?userId=njet2@app2&pwd=123456'
in /app2 main.lua