Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เสริมสร้างแอป Ruby ของคุณด้วย Secure JSON Web Tokens (JWT)

หากเว็บแอปพลิเคชันเกี่ยวข้องกับผู้ใช้ ข้อมูลของผู้ใช้ก็ควรได้รับการปกป้องและรักษาความปลอดภัย

การรักษาความปลอดภัยแอปพลิเคชันเว็บอาจมีความหมายหลายประการ ในโพสต์นี้ เราจะพูดถึงชุดย่อยของการรักษาความปลอดภัยเว็บที่เกี่ยวข้องกับการตรวจสอบสิทธิ์โดยใช้ JSON Web Tokens (JWT) และเฟรมเวิร์กแอปพลิเคชันเว็บ Ruby on Rails

มาเริ่มกันเลย!

โทเค็นเว็บ JSON คืออะไร

JSON Web Token เป็นมาตรฐานอินเทอร์เน็ตที่กำหนดโดย Internet Engineering Task Force (IETF) ว่าเป็น:"วิธีการที่ปลอดภัยต่อ URL ขนาดกะทัดรัดในการแสดงการอ้างสิทธิ์ที่จะถ่ายโอนระหว่างสองฝ่าย"

ในที่นี้ "การกล่าวอ้าง" หมายถึงข้อมูลหลายประเภทเกี่ยวกับเรื่องใดเรื่องหนึ่ง การอ้างสิทธิ์จะแสดงเป็นคู่ชื่อ/ค่า โดยที่ชื่อจะเป็นสตริงเสมอ และค่าสามารถเป็นค่า JSON ใดก็ได้

โครงสร้างพื้นฐานของโทเค็นเว็บ JSON

การเจาะลึกความซับซ้อนของ JWT นั้นอยู่นอกขอบเขตของโพสต์นี้ ถึงกระนั้นก็ตาม การรู้โครงสร้างของ JWT ก็คุ้มค่า

JWT ประกอบด้วยสามส่วน คั่นด้วยจุด:ส่วนหัว เพย์โหลด และลายเซ็นต์

ตัวอย่าง JWT อาจมีลักษณะดังนี้:

 

เพื่อให้ง่ายต่อการอ่าน แต่ละส่วนของโทเค็นจะเริ่มต้นด้วยบรรทัดใหม่ ในทางปฏิบัติ ชิ้นส่วนต่างๆ จะรวมกัน

โทเค็นนี้ถูกส่งจากเซิร์ฟเวอร์ไปยังไคลเอนต์ ลูกค้าส่งโทเค็นไปยังเซิร์ฟเวอร์เพื่อระบุตัวเองและดำเนินการตามคำขอ

ส่วนแรกคือส่วนหัว มีข้อมูลเกี่ยวกับอัลกอริทึมที่ใช้ในการสร้างโทเค็นและประเภทของโทเค็น หากถอดรหัสแล้ว เราจะได้สิ่งที่ต้องการ:

 

ส่วนที่สองคือเพย์โหลด ประกอบด้วยชุดข้อเรียกร้องเกี่ยวกับผู้ใช้ ในกรณีส่วนใหญ่ นี่จะเป็นไคลเอ็นต์ในการตั้งค่าไคลเอ็นต์-เซิร์ฟเวอร์ และอาจมีลักษณะดังนี้:

 

ลายเซ็น (ส่วนสุดท้ายของ JWT ที่ลงนาม) จะตรวจสอบโทเค็น มันถูกสร้างขึ้นโดยการเข้ารหัสส่วนหัวและเพย์โหลดโดยใช้การเข้ารหัส Base64url — RFC 4648 จากนั้นต่อเข้าด้วยกันด้วยตัวคั่นช่วงเวลา

โดยพื้นฐานแล้ว สิ่งที่เกิดขึ้นกับบิตลายเซ็นคือ:

 

07 เป็นอัลกอริทึมแฮชแบบคีย์ประเภทหนึ่งที่สร้างจากฟังก์ชันแฮช SHA-256 ที่แฮชลายเซ็น ทางเลือกของอัลกอริธึมการเข้ารหัสมาจาก 12 ในส่วนหัว หากโทเค็นไม่ได้ลงนาม จะมีเพียงส่วนหัวและเพย์โหลดที่ไม่มีลายเซ็น

JWT ปะทะ วิธีการตรวจสอบสิทธิ์อื่นๆ สำหรับแอป Ruby ของคุณ

JSON Web Tokens ดังที่ชื่อบอกเป็นนัย เป็นแบบอิงโทเค็น ในอีกด้านหนึ่ง เรามีการรับรองความถูกต้องตามเซสชัน:วิธีการตรวจสอบสิทธิ์ผู้ใช้แบบดั้งเดิม ขั้นตอนของการตรวจสอบสิทธิ์ตามเซสชันค่อนข้างแตกต่างจากการตรวจสอบสิทธิ์ตามโทเค็น

ขั้นตอนของการรับรองความถูกต้องตามเซสชันอาจมีลักษณะดังนี้:

  1. ผู้ใช้หรือลูกค้าส่งคำขอซึ่งมีข้อมูลรับรองผู้ใช้
  2. เซิร์ฟเวอร์จะตรวจสอบสิทธิ์ผู้ใช้ จัดเก็บเซสชัน และส่งคืน ID เซสชันที่จัดเก็บเป็นคุกกี้ในเบราว์เซอร์
  3. ไคลเอนต์ส่งคุกกี้พร้อมกับคำขอที่ตามมาไปยังเซิร์ฟเวอร์
  4. เซิร์ฟเวอร์ตรวจสอบข้อมูลเซสชันที่นำเสนอ และหากถูกต้อง จะตรวจสอบสิทธิ์ผู้ใช้และส่งคืนข้อมูลที่ร้องขอไปยังไคลเอนต์

แม้ว่าการตรวจสอบสิทธิ์ตามเซสชันส่วนใหญ่จะใช้สำหรับการเชื่อมต่อไคลเอนต์-เซิร์ฟเวอร์ แต่การตรวจสอบสิทธิ์ตามโทเค็นมักจะใช้กับการเชื่อมต่อเซิร์ฟเวอร์-เซิร์ฟเวอร์ (เช่น ระหว่าง API สองตัว)

อย่างไรก็ตาม ข้อแตกต่างที่สำคัญประการหนึ่งที่ควรทราบก็คือ ด้วยการตรวจสอบสิทธิ์ตามเซสชัน สถานะการตรวจสอบสิทธิ์จะได้รับการจัดการบนเซิร์ฟเวอร์ ในขณะที่โทเค็นได้รับการจัดการบนไคลเอนต์

เหตุใดจึงใช้ JWT สำหรับการตรวจสอบสิทธิ์

นอกจากจะใช้งานได้ค่อนข้างง่ายแล้ว ยังมีข้อดีอีกสองสามประการของการใช้ JSON Web Tokens สำหรับการตรวจสอบสิทธิ์ เช่น:

  • พวกเขาไร้สัญชาติ ซึ่งหมายความว่าไม่จำเป็นต้องมีที่เก็บเซสชัน โทเค็นนั้นมีข้อมูลผู้ใช้ทั้งหมด ดังนั้นจึงไม่จำเป็นต้องค้นหาฐานข้อมูลหรือเซิร์ฟเวอร์การตรวจสอบสิทธิ์สำหรับข้อมูลในแต่ละคำขอ
  • JWT โดยทั่วไปมีประสิทธิภาพมากกว่า กว่าวิธีการตรวจสอบสิทธิ์แบบดั้งเดิมส่วนใหญ่ (ตราบใดที่เซิร์ฟเวอร์ไม่ทำการค้นหากับฐานข้อมูลหรือจัดเก็บเพื่อตรวจสอบสิทธิ์ผู้ใช้) ทำให้มีประสิทธิภาพค่อนข้างมาก
  • พวกเขายังเสนอ การรับประกันความปลอดภัยที่แข็งแกร่ง ใน JWT ที่ลงนามนั้นให้การป้องกันเพื่อให้ผู้โจมตีหรือไคลเอนต์ไม่สามารถแก้ไขโทเค็นเพื่อเข้าถึงข้อมูลที่ได้รับการป้องกันได้

แนวทางปฏิบัติที่ดีที่สุดของ JWT สำหรับแอป Ruby

ดำเนินไปโดยไม่ได้บอกว่าคีย์ลับที่ใช้ในการเซ็นชื่อ JWT ควรมีความยาว สุ่ม และมีการผสมอักขระที่ซับซ้อน เพื่อให้แน่ใจว่าคีย์มีความปลอดภัยเพียงพอ และผู้โจมตีจะใช้กำลังดุร้ายได้ยาก

รหัสลับที่ Rails สร้างขึ้นนั้นปลอดภัยเป็นส่วนใหญ่ แต่การรับประกันความปลอดภัยจะถือเป็นโมฆะหากคุณส่งรหัสโดยไม่ตั้งใจและเปิดเผยมัน

สิ่งสำคัญคือต้องใช้ Transport Layer Security (TLS) เมื่อขนส่งโทเค็นระหว่างฝ่ายต่างๆ บนเครือข่าย TLS สามารถลดการโจมตีแบบแทรกกลางได้ (ทั้งโทเค็นและวิธีการตรวจสอบสิทธิ์แบบอิงเซสชันมีแนวโน้มที่จะเกิดการโจมตีดังกล่าว)

การใช้การรับรองความถูกต้อง JWT ในแอป Rails

มาดูขั้นตอนการตรวจสอบสิทธิ์แบบโทเค็น:

เสริมสร้างแอป Ruby ของคุณด้วย Secure JSON Web Tokens (JWT)

ต่างจากการพิสูจน์ตัวตนแบบอิงเซสชัน เทคนิคการพิสูจน์ตัวตนแบบ stateful ซึ่งเราใช้เซสชันเพื่อติดตามผู้ใช้ที่ผ่านการรับรองความถูกต้อง การรับรองความถูกต้องแบบโทเค็นด้วย JWT นั้นเป็นแบบไร้สัญชาติ ไม่จำเป็นต้องจัดเก็บข้อมูลใดๆ เกี่ยวกับสถานะการตรวจสอบสิทธิ์ของผู้ใช้บนเซิร์ฟเวอร์ ทำให้การออกแบบแอปพลิเคชันง่ายขึ้น

ในโพสต์นี้ เราจะถือว่าแอปพลิเคชันของเราแบ่งออกเป็นส่วนหน้าและส่วนหลัง การตรวจสอบสิทธิ์เกิดขึ้นที่แบ็กเอนด์ ดังนั้นเราจะสร้างแบ็กเอนด์ Rails API ด้วยการตรวจสอบสิทธิ์

โค้ดตัวอย่างในโพสต์นี้อิงจาก Rails 7.0.5 และ Ruby 3.2.2

การใช้ 26 และ 30 ทับทิมอัญมณี

เราจะต้องมีอัญมณีสองตัวสำหรับแอปพลิเคชันของเรา:42 และ 51 .

63 เป็นการใช้งาน Ruby ของมาตรฐาน RFC 7519 OAuth JSON Web Token 74 เป็นการผูก Ruby สำหรับ OpenBSD 87 อัลกอริธึมการแฮชรหัสผ่าน

คุณสามารถปฏิบัติตามโค้ดตัวอย่างได้ในที่เก็บโค้ดนี้

หมายเหตุ: 91 ไม่ใช่ทางออกเดียวสำหรับการทำงานกับ JWT อัญมณีที่รู้จักกันดีอีกอย่างคือ 101 ซึ่งให้การรับรองความถูกต้อง JWT สำหรับ Devise และ Rails แต่เราจะเน้นที่ 113 ในโพสต์นี้

มาเริ่มกันเลย

การสร้าง Rails API ของเรา

สิ่งแรกที่เราต้องการคือแอปพลิเคชัน API เราจะสร้างอันหนึ่งด้วย:

 

125 ตัวเลือกที่นี่จะกำหนดค่า Rails สแต็กที่เล็กกว่าสำหรับแอปพลิเคชัน API เท่านั้น

ภายใน Gemfile เราสามารถเพิ่มการอ้างอิงแรกของเราได้ 134 . เพชรเม็ดที่สองของเรา 149 มีอยู่แล้วใน Gemfile ของแอปพลิเคชัน Rails ที่สร้างขึ้นใหม่ — เราเพียงแต่ต้องยกเลิกการแสดงความคิดเห็นเท่านั้น

เราต้องการ 152 เพื่อแฮชรหัสผ่านผู้ใช้ในฐานข้อมูลอย่างปลอดภัย สิ่งสำคัญคือต้องทราบว่าเราจะไม่ใช้ 166 โดยตรง เราจะใช้ประโยชน์จาก 170 ของ Active Model วิธีการเรียนซึ่งขึ้นอยู่กับ 184 .

ละเว้นอัญมณีเริ่มต้นที่มาพร้อมกับแอปพลิเคชัน Rails ใหม่ Gemfile ของเราควรมีลักษณะดังนี้:

 

ตอนนี้เป็นเวลาที่ดีที่จะติดตั้งอัญมณีของเราด้วย 192 .

สร้าง 200 และ 216 โมเดล

ต่อไป เราจะสร้างสองรุ่น:228 และ 238 . 248 จะเป็นโมเดลที่เป็นตัวแทนผู้ใช้ และเราจะตรวจสอบความถูกต้องเพื่อให้สามารถเข้าถึงผลิตภัณฑ์ ซึ่งแสดงด้วย 259 รุ่น.

 

หลังจากดำเนินการย้ายข้อมูลด้วย 266 การตั้งค่าโมเดลของเราเสร็จสมบูรณ์ และสคีมาของเราพบได้ใน 275 ตอนนี้ควรมีลักษณะคล้ายกับสิ่งนี้:

 

สิ่งสำคัญคือต้องชี้ให้เห็นในขั้นตอนนี้ว่าเราจงใจเพิกเฉยต่อปัญหาที่อาจเกิดขึ้น เช่น ข้อจำกัดของฐานข้อมูล การตรวจสอบ การรับรองความเป็นเอกลักษณ์ ฯลฯ เพื่อประโยชน์ของโพสต์นี้ เราจะไม่คำนึงถึงการจัดการข้อผิดพลาด และอื่นๆ อีกมากมาย จุดประสงค์ของเราที่นี่คือเพื่อสาธิตการทำงานของ JWT ซึ่งเป็นรูปแบบหนึ่งของการตรวจสอบสิทธิ์แบบไร้สัญชาติเพื่อรักษาความปลอดภัยแอปพลิเคชัน Ruby ของเรา ในแอปพลิเคชันที่ใช้งานจริง คุณต้องแน่ใจว่าสิ่งเหล่านี้ครอบคลุมทั้งหมด

สร้าง 286 กระดาษห่ออัญมณี

ขั้นตอนต่อไปคือการสร้าง wrapper รอบ ๆ 292 อัญมณีที่เราติดตั้งไว้ก่อนหน้านี้ เราจะใช้ wrapper นี้เพื่อเข้ารหัสและถอดรหัสการอ้างสิทธิ์จากเซิร์ฟเวอร์ไปยังไคลเอนต์ สำหรับสิ่งนี้ เราจะสร้าง 304 โฟลเดอร์.

เหตุผลที่เราไม่ได้ใช้ 314 โฟลเดอร์ที่มาพร้อมกับ Rails คือไม่ได้โหลดอัตโนมัติ ทุกอย่างภายใต้ 327 ถูกโหลดอัตโนมัติและโหลดด้วยความกระตือรือร้นตามค่าเริ่มต้น ทำให้การตั้งค่าง่ายขึ้นในกรณีของเรา

คลาส wrapper ของเราอยู่ใน 338 และมีลักษณะดังนี้:

 

วิธีการหลักที่นี่คือ 349 — เพื่อเข้ารหัสข้อมูลผู้ใช้ — และ 357 — เพื่อถอดรหัสข้อมูลผู้ใช้ในเซิร์ฟเวอร์ในภายหลัง โปรดทราบว่าเรามอบหมายงานการเข้ารหัสและถอดรหัสให้กับ 362 อย่างไร อัญมณีผ่าน 375 และ 383 .

ณ จุดนี้ คุณสามารถทดสอบคลาสนี้ในคอนโซล Rails ได้แล้ว:

 

สังเกตว่าผลลัพธ์ของ 390 เป็นอย่างไร ถูกแบ่งออกเป็นสามส่วนตามช่วงเวลา โดยสร้างส่วนหัว เพย์โหลด และลายเซ็น เรามีบิตลายเซ็นเนื่องจากเราลงนามเพย์โหลดของเราด้วยรหัสลับที่ Rails มอบให้ผ่าน 407 .

กลับไปที่ 417 รุ่น

ตอนนี้เป็นเวลาที่ดีในการเยี่ยมชม 421 ของเรา รุ่นที่ 437 . สิ่งที่เราต้องทำที่นี่คือเพิ่ม 448 วิธีการเรียน:

 

450 แฮรหัสผ่านผู้ใช้ของเราอย่างปลอดภัยในฐานข้อมูล

สร้างผู้ใช้และผลิตภัณฑ์ตัวอย่างใน Rails

ตอนนี้เราสามารถกระโดดเข้าไปในคอนโซล Rails เพื่อสร้างผู้ใช้และผลิตภัณฑ์ตัวอย่างในฐานข้อมูลของเรา และทดสอบความปลอดภัยของแอปพลิเคชันของเรา:

 

คุณสามารถวางโค้ดชิ้นเดียวกันใน 462 ของคุณได้ ไฟล์เพื่อบันทึกการพิมพ์บางส่วนในกรณีที่คุณรีเซ็ตฐานข้อมูลของคุณ

การใช้ JWT ใน Rails Controllers

ในขั้นตอนถัดไป เราจะใช้การรักษาความปลอดภัยโดยใช้ JWT ภายในคอนโทรลเลอร์ของเรา จุดเริ่มต้นที่ดีคือ 476 ที่ 489 :

 

ที่นี่ เราสร้าง 496 วิธีการถอดรหัสโทเค็นเว็บ JSON ที่ผู้ใช้ส่งมาให้เรา ถ้าเราสามารถตรวจสอบโทเค็นได้สำเร็จ เราจะส่งคืน 504 วัตถุที่แสดงถึงผู้ใช้ที่ทำการร้องขอ เราสนใจเส้นทางแห่งความสุขเป็นส่วนใหญ่และจะเลี่ยงการตรวจสอบมากมาย

การกำหนด 519 วิธีการใน 523 และตั้งค่าเป็น 533 รักษาความปลอดภัยให้กับคอนโทรลเลอร์ทุกตัวที่สืบทอดมา การร้องขอไปยังคอนโทรลเลอร์อื่น ๆ จะต้องมี JWT ที่ถูกต้องเพื่อเข้าถึงคอนโทรลเลอร์เหล่านั้น (เนื่องจากคอนโทรลเลอร์อื่น ๆ ทุกตัวจะสืบทอดคอนโทรลเลอร์หลักนี้)

ต่อไปเราต้องมี 548 ซึ่งผู้ใช้สามารถส่งคำขอและรับ JSON Web Token ที่ลงนามแล้วจากเซิร์ฟเวอร์ของเรา คอนโทรลเลอร์นี้ควรอยู่ที่ 555 และอาจมีลักษณะดังนี้:

 

เมื่อคำขอมาถึงคอนโทรลเลอร์นี้ เนื่องจากเป็นผู้ใช้ที่ขอโทเค็น เราจึงไม่ต้องการตรวจสอบสิทธิ์พวกเขาตั้งแต่แรก วัตถุประสงค์ของตัวควบคุมนี้คือการตอบสนองด้วยโทเค็นที่ผู้ใช้สามารถใช้เพื่อเข้าถึงทรัพยากรที่เหลือบนเซิร์ฟเวอร์ของเรา ดังนั้นความต้องการ 568 บนบรรทัดที่สอง

ใน 576 การกระทำ (อันที่ผู้ใช้กดเพื่อรับโทเค็น) เราจะคว้า 586 และ 591 จากพารามิเตอร์ที่มาพร้อมกับคำขอไปยังคอนโทรลเลอร์นี้ หากเราสามารถตรวจสอบสิทธิ์ผู้ใช้ได้ — นั่นคือตรวจสอบว่าชื่อผู้ใช้และรหัสผ่านของพวกเขาตรงกับที่เราเก็บไว้ในฐานข้อมูลของเรา — จากนั้นเราจะนำเสนอโทเค็นที่ลงนามและข้อมูลเกี่ยวกับเวลาที่โทเค็นนั้นหมดอายุ

ในกรณีของเรา เราจะไม่ใช้ช่วงหมดอายุของโทเค็น แต่ในแอปพลิเคชันที่ใช้งานจริง ที่สามารถใช้เพื่อเพิกถอนการเข้าถึงทรัพยากรได้

เราจะทำตามขั้นตอนเหล่านี้ทั้งหมดโดยใช้ 606 ในภายหลัง

การทดสอบแอปพลิเคชัน Ruby ของเราด้วยทรัพยากรที่ได้รับการคุ้มครอง

เราได้กล่าวถึงโฟลว์บางส่วนในไดอะแกรมโฟลว์ของส่วน 'การใช้การรับรองความถูกต้อง JWT ในแอป Rails' ก่อนหน้านี้ เพื่อให้ครอบคลุมทุกสิ่งอย่างสมบูรณ์และทดสอบขั้นตอนทั้งหมดในแผนผังโฟลว์ เราจำเป็นต้องมีทรัพยากรในการปกป้อง

เรามี 619 รุ่นอยู่แล้ว ตอนนี้เราต้องการตัวควบคุมสำหรับรุ่นผลิตภัณฑ์และเส้นทางเพื่อเข้าถึงผลิตภัณฑ์และโทเค็น

มาสร้างคอนโทรลเลอร์ด้วย 627 ดังนั้นเราจึงมีบางอย่างเช่น:

 

แน่นอนว่าเราต้องการเส้นทางในการเข้าถึงตัวควบคุมเหล่านี้ 631 ของเรา ควรมีลักษณะดังนี้:

 

ตอนนี้เราจะทดสอบด้วย 640 เพื่อดูว่าทุกอย่างทำงานได้ตามที่คาดไว้หรือไม่ โปรดทราบว่าเรามีผู้ใช้ในการตรวจสอบสิทธิ์และมีทรัพยากรผลิตภัณฑ์ให้เข้าถึงอยู่แล้ว

ลองรับ JWT กับผู้ใช้ที่ไม่มีอยู่:

 

เราควรได้รับคำตอบต่อไปนี้:

 

ตอนนี้ลองแบบเดียวกันกับผู้ใช้ที่เราสร้างไว้ก่อนหน้านี้:

 

สิ่งนี้ควรให้โทเค็นเว็บ JSON ที่ลงนามแก่เราซึ่งอาจมีลักษณะดังนี้:

 

เก็บโทเค็นนี้ไว้สักครู่แล้วลองเข้าถึงทรัพยากรผลิตภัณฑ์ด้วยโทเค็นที่ไม่ถูกต้อง (ฉันเปลี่ยนอักขระแบบสุ่มในโทเค็น):

 

และเราควรได้รับ:

 

อย่างไรก็ตาม หากเราทำคำขอเดียวกันเพื่อเข้าถึงทรัพยากรผลิตภัณฑ์ด้วยโทเค็นที่ถูกต้องที่เราได้รับจากเซิร์ฟเวอร์ก่อนหน้านี้:

 

เราได้รับสิทธิ์ในการเข้าถึงทรัพยากรผลิตภัณฑ์:

 

แค่นั้นแหละ! เรารักษาความปลอดภัยแอปพลิเคชัน Ruby ของเราด้วยโทเค็นเว็บ JSON ได้สำเร็จ!

สรุป

ในโพสต์นี้ เราได้พูดคุยถึง JSON Web Tokens และวิธีการทำงาน อันดับแรกเราได้กล่าวถึงพื้นฐานของ JWT รวมถึงโครงสร้างและแนวปฏิบัติที่ดีที่สุดบางประการ จากนั้นเราใช้การตรวจสอบสิทธิ์ JWT อย่างง่ายโดยใช้ 655 อัญมณี

ฉันหวังว่าคุณจะพบว่าโพสต์นี้มีประโยชน์ ขอให้สนุกกับการเขียนโค้ด!

ปล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!