[NGINX-Demo] Demo-4: A/B Testing with JWT.

Supachai Jaturaprom
3 min readAug 17, 2021

อ้างอิงจาก บทความก่อนหน้า

Apply A/B tesing with NGINX (JWT)

4.1) ทำการแก้ไขไฟล์ docker-compose.yml ดังนี้

version: "3.7"
services:
nginx:
image: nginx-plus:latest
restart: always
ports:
- 80:80
volumes:
- ./conf/ab-testing-with-jwt-routing.conf:/etc/nginx/nginx.conf
- ./conf/api_secret.jwk:/etc/nginx/api_secret.jwk

จากสั่ง run ก็ใช้คำสั่ง

docker-compose up -d

ถ้าอยากดู logs แบบ realtime ของ nginx บน container ใช้คำสั่ง

docker-compose logs -f

4.2) ในการทดสอบนี้จะอ้างอิง nginx configuration จากไฟล์ ab-testing-with-jwt-routing.conf นี้

# JWT validation
auth_jwt "JWT Test Realm" token=$arg_myjwt;
auth_jwt_key_file /etc/nginx/api_secret.jwk;

และมีไฟล์​ api_secret.jwt

{"keys":
[{
"k":"bmdpbngxMjM",
"kty":"oct"
}]
}

กำหนดค่า k คือ symmetric key เป็น nginx123 โดย encode ด้วย base64 จากคำสั่งนี้

echo -n nginx123 | base64 | tr '+/' '-_' | tr -d '='

4.3) ทำการสร้าง jwt token แบบง่ายๆ โดยไปที่เว็บ https://jwt.io โดยกำหนดรายละเอียดดังนี้

Header:

{
"alg": "HS256",
"typ": "JWT"
}

Payload:

{
"exp": 1659812957,
"name": "Create New User",
"sub": "cuser",
"gname": "wheel",
"guid": "10",
"fullName": "John Doe",
"uname": "jdoe",
"uid": "111",
"sudo": true,
"dept": "IT",
"url": "http://secure.example.com"
}

Verify Signature:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),nginx123
)

ตัวอย่าง ที่ใช้ทดสอบ จะกำหนด "uid":"111"

และ "uid":"222" ดังนี้

4.4) ทดสอบเรียกโดย uid = 111 ส่ง jwt token ผ่านทาง myjwt บน URI

curl -i http://localhost/\?myjwt\=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTk4MTI5NTcsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.WAKIXeopm8TVeDSMiDi3M1CY-4l4cvZZO5iIlzayBP4

ทดสอบเรียกโดย โดย uid = 222 ส่ง jwt token ผ่านทาง myjwt บน URI

curl -i http://localhost/?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTk4MTI5NTcsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.j3kaqRXkbkJrCqTlWw0iKxlcgUVR_z3f5CLTaWPPS3M

4.5) ลองทำการส่ง jwt token ผ่านทาง Cookie กัน ลองเปลี่ยนตรง token จาก $arg_myjwt เป็น $cookie_myjwt

# JWT validation# auth_jwt "JWT Test Realm" token=$arg_myjwt;
auth_jwt "JWT Test Realm" token=$cookie_myjwt;
auth_jwt_key_file /etc/nginx/api_secret.jwk;

และทดสอบเรียกด้วย Cookie ดูครับ

curl -v --cookie myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTk4MTI5NTcsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.WAKIXeopm8TVeDSMiDi3M1CY-4l4cvZZO5iIlzayBP4 http://localhost

ถึงตรงนี้ ถือว่า เสร็จสิ้น การทดสอบเรื่อง A/B Testing ด้วย jwt ครับ

ส่งท้ายด้วย ในโหมด แอบซน และสงสัย****

0.1) ลองแกล้ง เปลี่ยนตัวอักษรบางตัว payload ของ jwt token ดูว่า NGINX สามารถตรวจสอบ หรือ Validate JWT Authentication ได้จริงไหม ?

curl -i http://localhost/?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTk4MTI5NTcsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMjIyIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ0.j3kaqRXkbkJrCqTlWw0iKxlcgUVR_z3f5CLTaWPPS3M

หมายเหตุ: ผมเปลี่ยนค่า J9 เป็น J0 ตรงท้าย payload jwt นะครับ เผื่อลืม format ของ jwt เป็น {Header.Payload.Signature}

ตัวอย่าง

0.2) ลองแกล้ง โดยทำการสร้าง jwt token ด้วย symmetric key เป็น nginx-community-thailand แต่ในไฟล์ api_secret.jwk ยังเหมือนเดิม.

curl -i http://localhost/?myjwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTk4MTI5NTcsIm5hbWUiOiJDcmVhdGUgTmV3IFVzZXIiLCJzdWIiOiJjdXNlciIsImduYW1lIjoid2hlZWwiLCJndWlkIjoiMTAiLCJmdWxsTmFtZSI6IkpvaG4gRG9lIiwidW5hbWUiOiJqZG9lIiwidWlkIjoiMTExIiwic3VkbyI6dHJ1ZSwiZGVwdCI6IklUIiwidXJsIjoiaHR0cDovL3NlY3VyZS5leGFtcGxlLmNvbSJ9.n9IiB-zJ2HF9D1mb7dMRg0YoIA2uNI-JLtvkCV5lDv4

ตัวอย่าง

0.3) ลองแกล้ง โดยกำหนดค่า exp หรือ Expiration time ซึ่งต้องกำหนดค่าเป็น seconds in unix epoch ครับ โดยผมจะเปลี่ยนจาก 1659812957 =>(2022-08-07 02:09:17) เป็น 1517417999 => (2018:01:31 23:59:59) นั้นหมายความว่า Expire แล้ว

$ date -r '1659812957' '+%Y-%m-%d %H:%M:%S'
2022-08-07 02:09:17
$ date -j -f "%Y:%m:%d %H:%M:%S" '2018:01:31 23:59:59' +%s
1517417999

ตัวอย่าง

ทำการสร้าง token ใหม่ลองดูจาก https://jwt.io

ลองเรียกเข้าไปด้วย jwt ที่หมดอายุแล้ว

หลังการทดสอบเสร็จแล้ว ก็ทำการลบ Container

docker-compose down

เรียบร้อยครับ จะเห็นว่า เราสามารถกำหนด กลุ่มของ A และ B ได้ ผ่านทาง JWT Claim ถ้าไม่ใช่ กลุ่ม A (uid=111) ก็ไปทาง B ทั้งหมด

ส่วนบทส่งท้าย คือ ผมอยากจำลอง หรือสร้างสถาณการณ์ต่างๆ ดูว่า ถ้าเกิดเหตุการณ์ต่างๆ แบบที่ผมสมมุติ ขึ้นแล้ว NGINX จะสามารถทำงานได้อย่างสมบูรณ์ หรือไม่

--

--