ทำให้ Token ซับซ้อนด้วย JWT ใน Spring Boot

Phayao Boonon
4 min readSep 14, 2018
Photo by Jon Moore on Unsplash

เราได้เพิ่มความปลอดภัยให้กับ Microservice ด้วยการใช้ OAuth2 protocal ซึ่งจากที่เราใช้ Basic Auth มาเป็นใช้ Token ในการ Authentication ของ Microservice แต่ใน Resource Server หรือตัว Microservice ของเราก็ต้องไปถาม Authorization Server ว่า Token ที่ได้มานั้นถูกต้องจริงไหม ทำให้ Microservice ไม่อิสระจะต้องขึ้นอยู่กับ Authorization Server ตลอดเวลาเมื่อมีการ Authentication

แต่ก็มีอีกวิธีหนึ่งคือการใช้ Token ที่เป็นแบบ JWT ที่ Authorization Server จะเข้ารหัสมาด้วย Key และที่ Resource Server ก็ใช้ Key เดียวกันในการถอดรหัสในการ Authentication ทำให้ Resource Server เป็นอิสระจาก Authorization Sever ได้ แต่ก็ต้องมี Key ที่ใช้ถอดรหัสกันได้

JWT คืออะไร

JSON Web Token (JWT) เป็น token ในรูปแบบ JSON สำหรับสร้าง access token ที่สามารถใส่ค่าบางอย่างไว้สำหรับตรวจสอบได้ ด้วยมาตรฐาน RFC 7519 ที่เป็น Stateless Authentication นั้นคือ state ของ user จะไม่ถูกเก็บไว้ที่ server ประกอบด้วย 3 ส่วน แยกกันด้วย จุด (.) คือ header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzY5NDU0MTksInVzZXJfbmFtZSI6ImpvaG4iLCJqdGkiOiI2M2Q2MDhhOS1hYTc4LTRmZTYtYWNlNy00NTE4ZjQ0YzNmODEiLCJjbGllbnRfaWQiOiJjbGllbnRJZCIsInNjb3BlIjpbIm9wZW5pZCJdfQ.zFycCczWj1kMKT3LXvbh-kpTK10sNT9y9gFDKxpnzok

Header

เป็นส่วนหัวของ token มี 2 ส่วนคือ hasing algorithm ที่ใช้และ type ของ token โดยจะถูกเข้ารหัสด้วย Base64Encoded ถอดรหัสจะได้ดังนี้

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

ใช้ hasing algorithm เป็น ​HS256 และ type แบบ JWT

Payload

เป็นส่วนของการ claim ของ token หรือเป็นส่วนของ ข้อมูล ของ tokenโดยจะถูกเข้ารหัสด้วย Base64Encoded ถอดรหัสจะได้ดังนี้

{
"exp": 1536945419,
"user_name": "john",
"jti": "63d608a9-aa78-4fe6-ace7-4518f44c3f81",
"client_id": "clientId",
"scope": [
"openid"
]
}

Signature

เป็นส่วนที่ทำให้เชื่อว่า token ไม่ได้ถูกเปลี่ยนแปลงระหว่างทาง ซึ่งเป็นค่าที่ได้จากการคำนวน จากสูตรนี้

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

เราสามารใช้เว็บ jwt.io ในการตรวจสอบ JWT token ได้โดยเว็บนี้จะ decode ให้และตรวจสอบ Signature ได้ด้วย

เพิ่ม JWT

Authorization Server

เรามา refactor Authorization Server ให้เปลี่ยนจากใช้ generated token มาเป็น JWT ด้วยการเปลี่ยนจาก application config มาเป็น Java config โดยสร้างคลาสที่ เป็น @Configuration ที่ extents จาก AuthorizationServerConfigurerAdapter โดยการแปลง token ด้วย JwtTokenStore ด้วย key 123456789 ดังนี้

ตัวอย่างใช้ key ง่ายๆๆ

Resource Server

ต่อมา refactor Resource Server ให้รองรับการ JWT token ด้วยการสร้างคลาสที่เป็น @Configuration ที่ extends มาจาก ResourceServerConfigurerAdapter ด้วยการตรวจสอบ token ที่ได้รับด้วย JwtTokenStore ด้วย key เดียวกับ Authorization Server ดังนี

จะเห็นได้ว่าการ Authentication แบบที่ใช้ key เดียวทั้งสองฝั่งนี้ใช้วิธี Symmetic cryptography ซึ่งเป็นวิธีที่ง่ายแต่ก็ต้องเก็บรักษา key นี้ไว้ให้ปลอดภัย

ทดสอบ JWT เบื้องต้น

หลังจากที่เราได้ refactor แล้วทั้ง 2 ฝั่งทั้ง Authorization Server และ Resource Server ก็ลองรันทั้ง 2 และลองใช้ Postman request ไปที่ Authorization Server จะได้ token ในรูปแบบของ JWT

ใช้ access token ที่ได้รับมา request ไปที่ Resource Server ถ้าถูกต้องจะได้ code 201 (Created) สำหรับ POST

ลองแก้ไขส่วน payload ของ JWT token และ request ไปที่ Resource Server อีกครั้งจะได้ code 401 (Unauthorized)

จะเห็นได้ว่า JWT token ใน OAuth2 ของเราทำงานได้ดี

ใช้ Asymmetric Key

สร้าง Java KeyStore

เริ่มแรกเราต้องสร้าง Java KeyStore file (JKS) ก่อน ด้วย keytoole โดยใช้คำสั่ง

$ keytool -genkeypair -alias myapi -keyalg RSA -keystore apikey.jks -deststoretype pkcs12

ใส่ keystore password และข้อมูลต่างๆ เสร็จแล้วก็จะได้ไฟล์ myapi.jks ซึ่งเก็บ key ของเราอยู่ทั้ง private key และ public key และ copy ไฟล์ไปใส่ใน resource ของ Authorization Server

Export Public Key

ต่อไปเราต้อง export Public Key ที่เราสร้าง Java KeyStore ด้วยคำสั่งนี้

$ keytool -list -rfc --keystore apikey.jks | openssl x509 -inform pem -pubkey

เก็บส่วนของ public key ไว้ในไฟล์ public.pem และ copy ไฟล์ไปใส่ใน resource ของ Resource Server

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtdYbbdEvjmP5wiTlGhFH
OemY+HCsk2vTbchJt0ed4o2DJh+x9cQ8DuyXMAnVPWNXq3kb1wxEIUXtkHTiaBHk
MoumUgB0Ri4AD13ZHgrl9wyHtPrAMHjIClBSua5mQakaA6s1X5RIJiCmtDERo3Rz
zpACqAzXqrIbVa79zIECenOdpcLrCE2Xas1EVWN5oIMlG1ZZ+PR1wgicipUDjriY
2rtuHwL4/Z8d3cOYuJJ2PVPW/Ubk44Lm8Y2dJ2Rg3/pn5PP5PyeV/PijUHxYbqMe
IOqaMGvdVk/Mk/Lw7PMnkQVJe1uxTMyeml5JgdvFbkNHitRFQ+WEDvBJfczZ7C45
UQIDAQAB
-----END PUBLIC KEY-----

Authorization Server

หลังจากที่ได้ Java KeyStore แล้วก็ refactor JwtAccessTokenConverter ให้อ่านไฟล์ apikey.jks ด้วย KeyStoreKeyFactory แล้ว setKeyPair

Resource Server

หลังจากที่เอา Public key ไปใส่ไว้ใน class resource ของ project แล้วก็ refactor JwtAccessTokenConverter ให้อ่านไฟล์ public.pem ได้เป็น String แล้วใส่เป็น verify key เพื่อใช้ verify JWT token ที่ request มา

ทดสอบ JWT (Asymmetric Key)

ลองทดสอบว่า JWT ด้วย Asymmetric Key ทำงานได้ไหม ก็ใช้วิธีทดสอบเหมือนข้างต้นเลย ถ้าทำงานได้จะได้ส่วน Signature ที่ได้จาก Authorization Server จะมีขนาดยาวกว่าตอนต้น

ใช้ JWT token ที่ได้มา request ไปที่ Resource Server ถูกต้องจะได้ code 201 (Created) สำหรับ POST

สรุป

จากที่ได้เพิ่ม JWT แทน token แบบเดิม ทำให้ Resource Server ไม่จำเป็นต้องตรวจสอบ token ทุกครั้งกับ Authorization Server โดยวิธีแรกจะใช้ key เดียวกันทั้ง 2 ฝั่ง ที่เรียกว่า Symmetric key ในการสร้างส่วนของ signature และวิธีที่สองเป็นใช้คนล่ะ key โดยที่ Authorization Server ใช้ private key และ Resource Server ใช้ public key ทำให้ไม่จำเป็นต้องรู้ key จริงว่าเป็นอะไรก็สามารถ verify signature ได้

--

--

Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish