กลยุทธ์ในการสร้างตัวระบุที่ไม่ซ้ำใครและปลอดภัยสำหรับใช้ในแอปพลิเคชันเว็บ "บางครั้งออฟไลน์"


47

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

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

ด้านล่างนี้เป็นโซลูชันที่เสนอของฉัน

ข้อกำหนดบางประการ

  1. รหัสควรไม่ซ้ำกันทั่วโลก (หรืออย่างน้อยไม่ซ้ำกันภายในระบบ)
  2. สร้างขึ้นบนไคลเอนต์ (เช่นผ่านทางจาวาสคริปต์ในเบราว์เซอร์)
  3. ปลอดภัย (ดังที่อธิบายไว้ข้างต้นและอื่น ๆ )
  4. ข้อมูลสามารถดู / แก้ไขโดยผู้ใช้หลายคนรวมถึงผู้ใช้ที่ไม่ได้เขียน
  5. ไม่ทำให้เกิดปัญหาเรื่องประสิทธิภาพที่สำคัญสำหรับแบ็กเอนด์ db (เช่น MongoDB หรือ CouchDB)

โซลูชันที่เสนอ

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

เมื่อผู้ใช้สร้างเรกคอร์ดใหม่พวกเขาสร้าง uuid ใหม่ใน javascript (สร้างโดยใช้ window.crypto เมื่อพร้อมใช้งานดูตัวอย่างที่นี่ ) รหัสนี้ถูกต่อกับ "โทเค็น id" ที่ผู้ใช้ได้รับเมื่อสร้างบัญชี คอมโพสิต id ใหม่นี้ (โทเค็น id เซิร์ฟเวอร์ + uuid ฝั่งไคลเอ็นต์) ตอนนี้เป็นตัวระบุที่ไม่ซ้ำกันสำหรับการบันทึก เมื่อผู้ใช้ออนไลน์และส่งบันทึกใหม่นี้ไปยังเซิร์ฟเวอร์เบื้องหลังเซิร์ฟเวอร์จะ:

  1. ระบุว่าเป็นการกระทำ "แทรก" (เช่นไม่ใช่การอัปเดตหรือลบ)
  2. ตรวจสอบทั้งสองส่วนของคีย์คอมโพสิตเป็น uuids ที่ถูกต้อง
  3. ตรวจสอบว่าส่วน "id token" ที่ให้ไว้ของคอมโพสิต id ถูกต้องสำหรับผู้ใช้ปัจจุบัน (เช่นตรงกับรหัสโทเค็นเซิร์ฟเวอร์ที่กำหนดให้กับผู้ใช้เมื่อพวกเขาสร้างบัญชีของพวกเขา)
  4. หากทุกอย่างเป็น copasetic ให้แทรกข้อมูลลงในฐานข้อมูล (โปรดระมัดระวังในการแทรกและไม่ใช่ "อัพเปอร์" เพื่อที่ว่าถ้าไอดีนั้นมีอยู่แล้วมันจะไม่อัปเดตระเบียนที่มีอยู่โดยไม่ได้ตั้งใจ)

การค้นหาอัปเดตและลบไม่จำเป็นต้องใช้ตรรกะพิเศษใด ๆ พวกเขาจะใช้รหัสสำหรับบันทึกในลักษณะเดียวกับแอปพลิเคชันทั่วไป

อะไรคือข้อดีของวิธีนี้?

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

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

  3. เนื่องจากโทเค็นรหัสผู้ใช้เชื่อมโยงกับบัญชีของพวกเขารหัสสามารถสร้างได้ในแซนด์บ็อกซ์ผู้ใช้โดยลูกค้าที่ได้รับการรับรองความถูกต้องเท่านั้น (เช่นที่ผู้ใช้ลงชื่อเข้าใช้สำเร็จ) สิ่งนี้มีวัตถุประสงค์เพื่อป้องกันไม่ให้ไคลเอนต์ที่เป็นอันตรายจากการสร้างรหัสที่ไม่ดีสำหรับผู้ใช้ แน่นอนถ้าผู้ใช้ auth โทเค็นถูกขโมยโดยลูกค้าที่เป็นอันตรายพวกเขาสามารถทำสิ่งที่ไม่ดี แต่เมื่อโทเค็นตรวจสอบความถูกต้องถูกขโมยบัญชีจะถูกบุกรุก แต่อย่างใด ในกรณีที่เกิดเหตุการณ์ขึ้นความเสียหายที่เกิดขึ้นจะถูก จำกัด เฉพาะบัญชีที่ถูกบุกรุก (ไม่ใช่ระบบทั้งหมด)

ความกังวลเกี่ยวกับ

นี่คือความกังวลของฉันเกี่ยวกับวิธีการนี้

  1. สิ่งนี้จะสร้างรหัสที่เป็นเอกลักษณ์เพียงพอสำหรับแอปพลิเคชันขนาดใหญ่หรือไม่ มีเหตุผลใดที่คิดว่าสิ่งนี้จะส่งผลให้เกิดการชนกันของรหัส? จาวาสคริปต์สามารถสร้าง uuid แบบสุ่มเพียงพอเพื่อให้มันใช้งานได้หรือไม่ ดูเหมือนว่า window.crypto นั้นมีอยู่ทั่วไปและโครงการนี้ต้องการเบราว์เซอร์ที่ทันสมัยพอสมควรแล้ว ( ตอนนี้คำถามนี้มีคำถาม SO แยกต่างหากของตัวเอง )

  2. มีช่องโหว่ใดบ้างที่ฉันหายไปซึ่งอาจทำให้ผู้ใช้ที่ประสงค์ร้ายสามารถโจมตีระบบได้หรือไม่?

  3. มีเหตุผลที่ต้องกังวลเกี่ยวกับประสิทธิภาพของฐานข้อมูลหรือไม่เมื่อทำการสอบถามคีย์คอมโพสิตที่ประกอบด้วย 2 uuids รหัสนี้จะถูกเก็บไว้อย่างไรเพื่อประสิทธิภาพที่ดีที่สุด? มีสองเขตข้อมูลแยกกันหรือเป็นเขตข้อมูลวัตถุเดียว จะมีวิธีที่ดีที่สุดสำหรับ Mongo vs Couch หรือไม่? ฉันรู้ว่าการมีคีย์หลักที่ไม่ต่อเนื่องกันอาจทำให้เกิดปัญหาประสิทธิภาพที่น่าทึ่งเมื่อทำการแทรก มันจะฉลาดกว่าหรือถ้ามีค่าที่สร้างขึ้นโดยอัตโนมัติสำหรับคีย์หลักและเก็บรหัสนี้เป็นฟิลด์แยกต่างหาก ( ตอนนี้คำถามนี้มีคำถาม SO แยกต่างหากของตัวเอง )

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

  5. ในกรณีที่มีการชนกันของรหัสสำหรับผู้ใช้ไม่มีวิธีการกู้คืนที่ง่าย ฉันคิดว่าไคลเอนต์สามารถสร้างรหัสใหม่ แต่ดูเหมือนว่าจะเป็นงานจำนวนมากสำหรับเคสที่ไม่ควรเกิดขึ้นจริง ๆ ฉันตั้งใจจะทิ้งสิ่งนี้ไว้

  6. ผู้ใช้ที่ผ่านการรับรองความถูกต้องเท่านั้นที่สามารถดูและ / หรือแก้ไขข้อมูล นี่เป็นข้อ จำกัด ที่ยอมรับได้สำหรับระบบของฉัน

ข้อสรุป

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


ฉันคิดว่าคำถามนี้อาจขัดขวางคุณstackoverflow.com/questions/105034/ … นอกจากนี้การอ่านนี้ให้ฉันชอบ GUID แต่ดูเหมือนว่าพวกเขาจะไม่ได้ใช้ภาษาจาวาสคริปต์
Rémi

2
มันทำให้ฉันรู้สึกว่า UUIDs เป็นไปตามข้อกำหนดที่ระบุไว้ 5 ข้อแล้ว ทำไมพวกเขาถึงไม่เพียงพอ?
Gabe

@Gabe ดูความคิดเห็นของฉันเกี่ยวกับเรื่องโกหก ryans โพสต์ด้านล่าง
herbrandson

เมตาการอภิปรายของคำถามนี้: meta.stackoverflow.com/questions/251215/…
gnat

"ลูกค้าที่เป็นอันตราย / เราเตอร์" - โกง
David Conrad

คำตอบ:


4

วิธีการของคุณจะทำงาน ระบบการจัดการเอกสารจำนวนมากใช้วิธีการนี้

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

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


2
ฉันคิดว่าใช้แฮชของ 2 เป็นรหัส อย่างไรก็ตามฉันไม่พบว่ามีวิธีที่เหมาะสมในการสร้าง sha256 ในเบราว์เซอร์ทั้งหมดที่ฉันต้องการสนับสนุน :(
herbrandson

12

คุณต้องแยกข้อกังวลทั้งสอง:

  1. การสร้าง ID: ลูกค้าจะต้องสามารถสร้างตัวระบุที่ไม่ซ้ำกันในระบบกระจาย
  2. ข้อกังวลด้านความปลอดภัย: ไคลเอนต์ต้องมีโทเค็นการตรวจสอบความถูกต้องของผู้ใช้ที่ถูกต้องและโทเค็นการตรวจสอบความถูกต้องนั้นถูกต้องสำหรับวัตถุที่กำลังสร้าง / แก้ไข

การแก้ปัญหาของสองสิ่งนี้น่าเสียดายที่แยกจากกัน แต่โชคดีที่พวกเขาไม่เข้ากัน

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

เมื่อจัดการอย่างถูกต้องการชนกันจะไม่ก่อให้เกิดปัญหาด้านความปลอดภัยอย่างแท้จริง (ผู้ใช้หรือลูกค้าจะถูกขอให้ลองการดำเนินการกับ UUID อื่นอีกครั้ง)


นี่เป็นจุดที่ดีจริงๆ บางทีนั่นคือทั้งหมดที่จำเป็นและฉันก็คิดถึงมัน อย่างไรก็ตามฉันมีข้อกังวลเล็กน้อยเกี่ยวกับวิธีการนี้ ที่ใหญ่ที่สุดคือ javascript ที่สร้างขึ้น uuids ดูเหมือนจะไม่สุ่มเหมือนที่หวังไว้ (ดูความคิดเห็นที่stackoverflow.com/a/2117523/13181สำหรับผู้ถูกกักขัง) ดูเหมือนว่า window.crypto ควรแก้ไขปัญหานี้ แต่ดูเหมือนว่าจะไม่สามารถใช้ได้กับเบราว์เซอร์ทุกรุ่นที่ฉันต้องการสนับสนุน
herbrandson

ดำเนินการต่อ ... ฉันชอบข้อเสนอแนะของคุณในการเพิ่มรหัสในไคลเอนต์ที่จะสร้าง uuid ใหม่ในกรณีที่มีการปะทะกัน อย่างไรก็ตามดูเหมือนว่าสำหรับฉันแล้วสิ่งนี้จะนำเสนอข้อกังวลที่ฉันมีในบทความของฉันใหม่ตามจุดที่ 1 ของส่วน "อะไรคือข้อดีของวิธีการนี้" ฉันคิดว่าถ้าฉันไปเส้นทางนั้นฉันควรจะดีกว่าเพียงแค่สร้างรหัสชั่วคราวบนฝั่งไคลเอ็นต์แล้วอัปเดตพวกเขาด้วย "รหัสสุดท้าย" จากเซิร์ฟเวอร์เมื่อเชื่อมต่อแล้ว
herbrandson

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

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

ขอบคุณสำหรับความคิดเห็นของคุณ มันบังคับให้ฉันต้องอธิบายความคิดของฉันจริงๆ! มีเหตุผลที่ฉันระวังวิธีการของคุณและฉันลืมมันไปตลอดทาง :) ความกลัวของฉันเชื่อมโยงกับ RNG ที่ไม่ดีในเบราว์เซอร์จำนวนมาก สำหรับรุ่น uuid เราน่าจะชอบ RNG ที่มีการเข้ารหัสสูง เบราว์เซอร์ที่ใหม่กว่าจำนวนมากมีสิ่งนี้ผ่านทาง window.crypto แต่เบราว์เซอร์ที่เก่ากว่านั้นไม่มี ด้วยเหตุนี้จึงเป็นไปได้ที่ผู้ใช้ที่ประสงค์ร้ายจะทราบว่าผู้ใช้รายอื่นคือ RNG และรู้ uuid ถัดไปที่จะสร้าง นี่คือส่วนที่รู้สึกว่าสามารถถูกโจมตีได้
herbrandson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.