passport.js รับรองความถูกต้องสงบ


155

มีวิธีจัดการกับการรับรองความถูกต้อง (ตัวอย่างเช่นภายในและ Facebook) โดยใช้ passport.js ผ่าน RESTful API แทนที่จะใช้ผ่านเว็บอินเตอร์เฟส

ข้อกังวลเฉพาะเจาะจงคือการจัดการการส่งผ่านข้อมูลจากการเรียกกลับไปยังการตอบกลับแบบสงบ (JSON) กับการใช้ res.send ทั่วไป ({data: req.data}), การตั้งค่าจุดเริ่มต้น / การล็อกอินที่เปลี่ยนเส้นทางไปยัง Facebook (/ ล็อกอินไม่สามารถ เข้าถึงได้ผ่าน AJAX เนื่องจากไม่ใช่การตอบสนอง JSON - เป็นการเปลี่ยนเส้นทางไปยัง Facebook ด้วยการโทรกลับ)

ฉันพบhttps://github.com/halrobertson/test-restify-passport-facebookแต่ฉันมีปัญหาในการทำความเข้าใจ

นอกจากนี้ passport.js จะจัดเก็บข้อมูลรับรองความถูกต้องอย่างไร เซิร์ฟเวอร์ (หรือบริการเป็นบริการนี้) ได้รับการสนับสนุนโดย MongoDB และฉันคาดหวังว่าข้อมูลประจำตัว (ล็อกอินและแฮชของ pw) จะถูกเก็บไว้ที่นั่น แต่ฉันไม่ทราบว่า passport.js มีความสามารถประเภทนี้หรือไม่


ตั้งแต่คุณยังใหม่กับโหนดเริ่มต้นที่ง่ายและตรวจสอบการประยุกต์ใช้ตัวอย่างpassport-facebookสำหรับ หลังจากที่คุณได้รับการทำงานขั้นตอนต่อไปคือการเริ่มทำความเข้าใจวิธีการทำงานของ Passport และวิธีการเก็บหนังสือรับรอง เชื่อมต่อกับ Restify ( ดูที่นี่สำหรับเวอร์ชันที่อัปเดตของเวอร์ชันที่คุณพูดถึง) จะเป็นหนึ่งในขั้นตอนสุดท้าย (หรือคุณสามารถใช้ส่วนต่อประสาน REST ใน Express)
robertklep

คำตอบ:


312

มีคำถามมากมายถามที่นี่และดูเหมือนว่าแม้ว่าคำถามจะถูกถามในบริบทของ Node และ passport.js คำถามจริงเกี่ยวกับเวิร์กโฟลว์มากกว่าวิธีการทำเช่นนี้ด้วยเทคโนโลยีเฉพาะ

ลองใช้การตั้งค่าตัวอย่าง @ Keith ปรับเปลี่ยนบิตเพื่อเพิ่มความปลอดภัย:

  • เว็บเซิร์ฟเวอร์ที่https://example.comให้บริการแอปไคลเอนต์ Javascript หน้าเดียว
  • บริการเว็บสงบที่https://example.com/apiให้การสนับสนุนเซิร์ฟเวอร์แอปลูกค้ามากมาย
  • เซิร์ฟเวอร์ใช้งานในโหนดและ passport.js
  • เซิร์ฟเวอร์มีฐานข้อมูล (ชนิดใดก็ได้) พร้อมตาราง "ผู้ใช้"
  • เสนอชื่อผู้ใช้ / รหัสผ่านและ Facebook Connect เป็นตัวเลือกการตรวจสอบสิทธิ์
  • ไคลเอนต์ที่หลากหลายทำการร้องขอ REST https://example.com/api
  • อาจจะมีลูกค้าอื่น ๆ (ปพลิเคชันโทรศัพท์เป็นต้น) ที่ใช้บริการเว็บที่แต่ไม่ทราบเกี่ยวกับเว็บเซิร์ฟเวอร์ที่https://example.com/apihttps://example.com

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

การตรวจสอบชื่อผู้ใช้ / รหัสผ่าน

ลองดูว่าการรับรองความถูกต้องแบบเก่าทำงานอย่างไรก่อน

  • ผู้ใช้เชื่อมต่อกับ https://example.com
  • เซิร์ฟเวอร์ให้บริการแอปพลิเคชัน Javascript ที่มีรูปแบบหลากหลายซึ่งแสดงผลหน้าเริ่มต้น บางคนในหน้านี้มีแบบฟอร์มเข้าสู่ระบบ
  • ส่วนต่างๆของแอพพลิเคชั่นหน้าเดียวนี้ยังไม่ได้มีการเติมข้อมูลเนื่องจากผู้ใช้ไม่ได้เข้าสู่ระบบส่วนเหล่านี้ทั้งหมดมีฟังเหตุการณ์ในเหตุการณ์ "เข้าสู่ระบบ" ทั้งหมดนี้คือสิ่งที่ฝั่งไคลเอ็นต์เซิร์ฟเวอร์ไม่ทราบเหตุการณ์เหล่านี้
  • ผู้ใช้เข้าสู่ระบบ / รหัสผ่านของเขา / เธอและกดปุ่มส่งซึ่งทริกเกอร์จัดการจาวาสคริปต์เพื่อบันทึกชื่อผู้ใช้และรหัสผ่านในตัวแปรฝั่งไคลเอ็นต์ จากนั้นตัวจัดการนี้จะเรียกเหตุการณ์ "เข้าสู่ระบบ" อีกครั้งนี้เป็นทุกการกระทำด้านลูกค้า, ข้อมูลประจำตัวที่ไม่ได้ถูกส่งไปยังเซิร์ฟเวอร์
  • ผู้ฟังเหตุการณ์ "เข้าสู่ระบบ" ถูกเรียกใช้ ตอนนี้แต่ละรายการจำเป็นต้องส่งคำขออย่างน้อยหนึ่งคำขอไปยัง RESTful API ที่https://example.com/apiเพื่อรับข้อมูลเฉพาะของผู้ใช้เพื่อแสดงผลในหน้า ทุกคำขอเดียวที่พวกเขาส่งไปยังบริการเว็บจะมีชื่อผู้ใช้และรหัสผ่านซึ่งอาจอยู่ในรูปแบบของการรับรองความถูกต้องHTTP พื้นฐานเนื่องจากบริการที่สงบไม่ได้รับอนุญาตให้รักษาสถานะไคลเอนต์จากคำขอหนึ่งไปยังอีก เนื่องจากบริการทางเว็บนั้นอยู่บน HTTP ที่ปลอดภัยรหัสผ่านจึงถูกเข้ารหัสอย่างปลอดภัยในระหว่างการขนส่ง
  • บริการเว็บที่https://example.com/apiได้รับคำขอจำนวนมากแต่ละคำขอมีข้อมูลการตรวจสอบสิทธิ์ ชื่อผู้ใช้และรหัสผ่านในการร้องขอแต่ละครั้งจะถูกตรวจสอบกับฐานข้อมูลผู้ใช้และหากพบว่าถูกต้องจะมีการเรียกใช้ฟังก์ชันที่ร้องขอและข้อมูลจะถูกส่งคืนไปยังไคลเอ็นต์ในรูปแบบ JSON หากชื่อผู้ใช้และรหัสผ่านไม่ตรงกับข้อผิดพลาดจะถูกส่งไปยังลูกค้าในรูปแบบของรหัสข้อผิดพลาด 401 HTTP
  • แทนที่จะบังคับให้ลูกค้าส่งชื่อผู้ใช้และรหัสผ่านทุกคำขอคุณสามารถใช้ฟังก์ชัน "get_access_token" ในบริการ RESTful ของคุณซึ่งใช้ชื่อผู้ใช้และรหัสผ่านและตอบกลับด้วยโทเค็นซึ่งเป็นแฮชการเข้ารหัสลับบางอย่างที่ไม่เหมือนใคร วันที่ที่เกี่ยวข้องกับมัน โทเค็นเหล่านี้จะถูกเก็บไว้ในฐานข้อมูลกับผู้ใช้แต่ละคน จากนั้นไคลเอ็นต์จะส่งโทเค็นการเข้าถึงในคำขอถัดไป โทเค็นการเข้าถึงจะถูกตรวจสอบกับฐานข้อมูลแทนชื่อผู้ใช้และรหัสผ่าน
  • แอปพลิเคชันไคลเอนต์ที่ไม่ใช่เบราว์เซอร์เช่นแอพโทรศัพท์ทำเช่นเดียวกับด้านบนพวกเขาขอให้ผู้ใช้ป้อนข้อมูลประจำตัวของเขา / เธอแล้วส่งพวกเขา (หรือโทเค็นการเข้าถึงที่สร้างขึ้นจากพวกเขา) ทุกคำขอไปยังบริการเว็บ

จุดที่สำคัญใช้เวลาห่างจากตัวอย่างนี้คือบริการเว็บสงบต้องตรวจสอบทุกครั้งที่มีการร้องขอ

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

การรับรองความถูกต้องของ Facebook

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

ดังนั้นเรามาดูกันว่าสิ่งต่าง ๆ เปลี่ยนแปลง:

  • ผู้ใช้เชื่อมต่อกับ https://example.com
  • เซิร์ฟเวอร์ให้บริการแอปพลิเคชัน Javascript ที่มีรูปแบบหลากหลายซึ่งแสดงผลหน้าเริ่มต้น บางคนในหน้านี้มีแบบฟอร์มเข้าสู่ระบบที่มีปุ่ม "เข้าสู่ระบบด้วย Facebook"
  • ผู้ใช้คลิกที่ปุ่ม "เข้าสู่ระบบด้วย Facebook" ซึ่งเป็นเพียงการเชื่อมโยงว่าการเปลี่ยนเส้นทางไป https://example.com/auth/facebook(ตัวอย่าง)
  • https://example.com/auth/facebookเส้นทางจะถูกจัดการโดย passport.js (ดูเอกสารประกอบ )
  • ผู้ใช้ทุกคนเห็นว่าหน้านั้นเปลี่ยนไปและตอนนี้พวกเขาอยู่ในหน้าโฮสต์ของ Facebook ซึ่งพวกเขาจำเป็นต้องลงชื่อเข้าใช้และอนุญาตแอปพลิเคชันเว็บของเรา สิ่งนี้อยู่นอกเหนือการควบคุมของเราอย่างสมบูรณ์
  • บันทึกของผู้ใช้ใน Facebook และให้สิทธิ์ในการประยุกต์ใช้ของเราดังนั้น Facebook ตอนนี้เปลี่ยนเส้นทางกลับไปยัง URL เรียกกลับที่เรากำหนดค่าในการตั้งค่า passport.js ซึ่งต่อไปนี้ตัวอย่างในเอกสารเป็นhttps://example.com/auth/facebook/callback
  • ตัวจัดการ passport.js สำหรับhttps://example.com/auth/facebook/callbackเส้นทางจะเรียกใช้ฟังก์ชันการเรียกกลับที่ได้รับโทเค็นการเข้าถึง Facebook และข้อมูลผู้ใช้บางส่วนจาก Facebook รวมถึงที่อยู่อีเมลของผู้ใช้
  • ด้วยอีเมลเราสามารถค้นหาผู้ใช้ในฐานข้อมูลของเราและจัดเก็บโทเค็นการเข้าถึง Facebook ด้วย
  • สิ่งสุดท้ายที่คุณทำในการติดต่อกลับของ Facebook คือการเปลี่ยนเส้นทางกลับไปที่แอปพลิเคชันไคลเอนต์ที่หลากหลาย แต่คราวนี้เราต้องส่งชื่อผู้ใช้และโทเค็นการเข้าถึงไปยังลูกค้าเพื่อให้สามารถใช้งานได้ สามารถทำได้หลายวิธี ตัวอย่างเช่นตัวแปร Javascript สามารถเพิ่มไปยังหน้าผ่านเครื่องมือแม่แบบฝั่งเซิร์ฟเวอร์มิฉะนั้นคุกกี้สามารถส่งคืนพร้อมข้อมูลนี้ (ขอบคุณ @RyanKimber สำหรับการชี้ปัญหาด้านความปลอดภัยเมื่อส่งข้อมูลนี้ใน URL ตามที่ฉันแนะนำไว้ในตอนแรก)
  • ดังนั้นตอนนี้เราเริ่มแอปหน้าเดียวอีกครั้ง แต่ลูกค้ามีชื่อผู้ใช้และโทเค็นการเข้าถึง
  • แอปพลิเคชันไคลเอนต์สามารถเรียกใช้เหตุการณ์ "เข้าสู่ระบบ" ได้ทันทีและให้ส่วนต่าง ๆ ของแอปพลิเคชันร้องขอข้อมูลที่ต้องการจากบริการเว็บ
  • คำขอทั้งหมดที่ส่งไปhttps://example.com/apiจะรวมโทเค็นการเข้าถึง Facebook สำหรับการรับรองความถูกต้องหรือโทเค็นการเข้าถึงของแอปพลิเคชันที่สร้างขึ้นจากโทเค็นของ Facebook ผ่านฟังก์ชั่น "get_access_token" ใน REST API
  • แอปที่ไม่ใช่เบราว์เซอร์ทำให้มันยากขึ้นเล็กน้อยที่นี่เนื่องจาก OAuth ต้องการเว็บเบราว์เซอร์สำหรับการลงชื่อเข้าใช้ในการลงชื่อเข้าใช้จากโทรศัพท์หรือแอพเดสก์ท็อปคุณจะต้องเริ่มเบราว์เซอร์เพื่อเปลี่ยนเส้นทางไปยัง Facebook ต้องการวิธีที่เบราว์เซอร์จะส่งโทเค็นการเข้าถึง Facebook กลับไปยังแอปพลิเคชันผ่านกลไกบางอย่าง

ฉันหวังว่าสิ่งนี้จะตอบคำถามส่วนใหญ่ได้ แน่นอนคุณสามารถแทนที่ Facebook ด้วย Twitter, Google หรือบริการตรวจสอบความถูกต้องตาม OAuth อื่น ๆ

ฉันสนใจที่จะรู้ว่ามีใครบางคนมีวิธีที่ง่ายกว่าในการจัดการกับเรื่องนี้


5
ขอบคุณสำหรับการตอบกลับอย่างละเอียด เพียงแค่หนึ่งคำถาม: คุณบอกว่าและยังที่คุณพูดEvery single request they send to the web service will include the username and password you can have a "get_access_token" function in your RESTful serviceดูเหมือนจะขัดแย้งกันที่จะบอกว่า REST จำเป็นต้องไร้สัญชาติ แต่การจัดเก็บการเข้าถึงโทเค็นการเข้าถึงเซิร์ฟเวอร์นั้นใช้ได้เนื่องจากการจัดเก็บโทเค็นการเข้าถึงนั้นหมายความว่าเซิร์ฟเวอร์นั้นเป็นสถานะในขณะนี้ ฉันขอขอบคุณการชี้แจงหรือเหตุผลอันสมควรเกี่ยวกับเรื่องนี้ ขอบคุณ! :)
ryanrhee

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

1
@Dexter: ในกรณีการเข้าสู่ระบบแบบดั้งเดิมผู้ใช้ป้อนชื่อผู้ใช้และรหัสผ่านลงในแบบฟอร์มและเมื่อเขากดปุ่มส่งข้อมูลนี้จะถูกโพสต์ไปยังเว็บเซิร์ฟเวอร์ ในกรณีนี้สิ่งนี้จะไม่เกิดขึ้นผู้ใช้กรอกแบบฟอร์มและเมื่อเขาพบ Hit ตัวจัดการส่งจาวาสคริปต์ (เหตุการณ์ onClick ในปุ่มส่ง) จับข้อมูลและเก็บไว้ในบริบทของลูกค้า ฉันยังไม่มีตัวอย่างพร้อมที่จะแสดง แต่ระวังส่วนที่สองของบทช่วยสอนนี้ในบล็อกของฉันที่ฉันจะแสดงให้เห็นว่ามันทำอย่างไร: blog.miguelgrinberg.com/post/…
Miguel

2
นี่เป็นความคิดที่ดีมาก แต่มีการกำกับดูแลหรือข้อผิดพลาดที่สำคัญอย่างหนึ่ง เมื่อจัดการการเข้าสู่ระบบ Facebook (หรือ Github, twitter และอื่น ๆ ) มันจะเป็นที่นิยมมากในการส่งโทเค็นกลับไปยังไคลเอนต์ในคุกกี้และจากนั้นลบคุกกี้บนฝั่งไคลเอ็นต์เมื่อค้นพบ การส่งโทเค็นเป็นส่วนหนึ่งของสตริง URL จะเพิ่ม URL นี้ไปยังประวัติเบราว์เซอร์และ (หากมีการจัดการสิ่งที่ไม่ถูกต้อง) อาจนำไปสู่ ​​URL นี้ที่เบราว์เซอร์ร้องขอ ทั้งทำให้โทเค็นสามารถมองเห็นได้ ทุกคนที่คัดลอก URL สามารถหลอกผู้ใช้นี้ในแอปพลิเคชันของคุณ
Ryan Kimber

1
@ นาธาน: รับรองความถูกต้องขั้นพื้นฐานผ่าน https มีความปลอดภัยดังนั้นใช่ว่าเป็นกลไกที่ยอมรับได้
Miguel

11

ฉันซาบซึ้งมาก @ คำอธิบายของมิเกลเกี่ยวกับการไหลที่สมบูรณ์ในแต่ละกรณี แต่ฉันต้องการเพิ่มบางส่วนในส่วนการรับรองความถูกต้องของ Facebook

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

นอกจากนี้คุณยังสามารถใช้จุดสิ้นสุด API เดียวกันสำหรับแอปพลิเคชันมือถือเช่นกัน เพียงใช้ Android / iOS SDK สำหรับ Facebook รับ Facebook access_token ที่ส่วนท้ายของไคลเอ็นต์และส่งไปยังเซิร์ฟเวอร์

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

แค่ 2 เซ็นต์ของฉัน ..


1
สิ่งนี้สำคัญมากเพราะวิธีนี้คุณสามารถทำ Ajax auth ได้เพียงหน้าเดียวโดยใช้ Facebook โทเค็นนั้นปลอดภัยพอ ๆ กับการตรวจสอบความถูกต้องของเซสชันความแตกต่างเพียงอย่างเดียวคือโทเค็นสามารถส่งต่อไปยังโดเมนอื่นและใช้เซสชันในโดเมนหนึ่งเท่านั้น เมื่อใช้ expressjs และพาสปอร์ตคุณสามารถสร้าง API ที่ช่วยประหยัดสถานะและใช้ประโยชน์จากการตรวจสอบเซสชัน
jperelli

api javascript นั้นยอดเยี่ยมถ้าคุณต้องการรับรองความถูกต้องของผู้ใช้เพื่อดำเนินการกับ Facebook แต่ไม่มีประโยชน์หากคุณต้องการตรวจสอบผู้ใช้กับเซิร์ฟเวอร์ / ฐานข้อมูลเท่าที่ฉันสามารถบอกได้
James Westgate

4
คุณยังสามารถใช้วิธีที่อธิบายโดยมิเกลข้างต้น แต่จากนั้นออกโทเค็น JWT ของคุณเองเป็นคุกกี้เมื่อเปลี่ยนเส้นทางไคลเอนต์ในการโทรกลับรับรองความถูกต้อง สิ่งนี้ช่วยให้ทั้งสองโลกดีที่สุด - แอปพลิเคชันหน้าเดียวของคุณสามารถกังวลเกี่ยวกับการรับรองความถูกต้องเพียงหนึ่งประเภท (JWT) ในขณะที่รักษาความปลอดภัยในระดับเดียวกันและให้ความยืดหยุ่นในการรองรับการลงชื่อเข้าใช้ทางสังคมใด ๆ JavaScript API เฉพาะสำหรับแต่ละเครือข่ายสังคม (Facebook, Twitter, LinkedIn, Google, ฯลฯ ) นอกจากนี้ยังช่วยให้คุณรักษาการรองรับสไตล์ AJAX สำหรับชื่อผู้ใช้ / รหัสผ่านและการเข้าถึง REST
Ryan Kimber

Facebook Javascript SDK ปัจจุบันไม่สามารถทำงานกับ Chrome iOS อาจเป็นปัญหาสำหรับบางคน
demisx

@RyanKimber คุณสามารถเขียน repo คอมไพล์ขนาดเล็กหรือคล้ายกันซึ่งนี่เป็นตัวอย่างที่ฉันติดอยู่โดยสิ้นเชิง
Simon Dragsbæk

3

นี่เป็นบทความที่ยอดเยี่ยมที่ฉันพบว่าสามารถช่วยคุณตรวจสอบสิทธิ์ด้วย:

  • Facebook
  • พูดเบาและรวดเร็ว
  • Google
  • การตรวจสอบท้องถิ่น

การพิสูจน์ตัวตนโหนดง่าย: การตั้งค่าและท้องถิ่น


ลิงก์ของคุณไม่ได้นำไปสู่บทความ แต่ไปยังรายการบทความที่ติดแท็กด้วย 'javascript'
เปาโล Oliveira

1
ขอบคุณ อัพเดทลิงค์ พวกเขาเปลี่ยนไปถ้ามี
myusuf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.