การรับรองความถูกต้องโดยใช้โทเค็น REST API


122

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

สิ่งนี้ดีไปกว่าการกำหนดให้ไคลเอนต์ใช้ HTTP Basic Auth ในแต่ละคำขอและแคชการโทรไปยังฝั่งเซิร์ฟเวอร์ของบริการตรวจสอบความถูกต้องหรือไม่?

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

ปัจจุบันโทเค็นได้รับในลักษณะนี้:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

api_key, timestampและverifierถูกต้องตามคำขอทั้งหมด "ตัวยืนยัน" ส่งคืนโดย:

sha1(timestamp + api_key + shared_secret)

ความตั้งใจของฉันคืออนุญาตให้โทรจากบุคคลที่รู้จักเท่านั้นและเพื่อป้องกันไม่ให้มีการโทรซ้ำแบบคำต่อคำ

ดีพอหรือยัง? Underkill? Overkill?

ด้วยโทเค็นในมือลูกค้าสามารถรับทรัพยากร:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

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


2
แทนที่จะใช้ sha1 (timestamp + api_key + shard_secret) คุณควรใช้ hmac (shared_secret, timpestamp + api_key) เพื่อการแฮชการรักษาความปลอดภัยที่ดีขึ้นen.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco

@ MiguelA.Carrasco และดูเหมือนว่าจะเป็นฉันทามติในปี 2560 ที่ bCrypt เป็นเครื่องมือแฮชใหม่
Shawn

คำตอบ:


94

ให้ฉันแยกทุกอย่างออกและแก้ไขปัญหาแต่ละอย่างแยกกัน:

การรับรอง

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

โหลดเซิร์ฟเวอร์รับรองความถูกต้อง

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

ความปลอดภัยในการส่ง

หากสามารถใช้การเชื่อมต่อ SSL นั่นคือทั้งหมดที่มีการเชื่อมต่อนั้นปลอดภัย * เพื่อป้องกันการดำเนินการหลายครั้งโดยไม่ได้ตั้งใจคุณสามารถกรอง URL หลายรายการหรือขอให้ผู้ใช้รวมส่วนประกอบแบบสุ่ม ("nonce") ไว้ใน URL

url = username:key@myhost.com/api/call/nonce

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

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:one_time_key@myhost.com/api/call

เป็นความจริงที่ว่านี่เป็นเรื่องยาก เนื่องจากคุณไม่ได้ใช้โซลูชันระดับโปรโตคอล (เช่น SSL) ดังนั้นจึงเป็นความคิดที่ดีที่จะจัดหา SDK บางประเภทให้กับผู้ใช้อย่างน้อยพวกเขาก็ไม่ต้องดำเนินการเอง หากคุณจำเป็นต้องทำด้วยวิธีนี้ฉันพบว่าระดับความปลอดภัยเหมาะสม (just-right-kill)

ที่เก็บข้อมูลลับที่ปลอดภัย

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

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

(*) แก้ไข: การเชื่อมต่อ SSL ไม่ควรได้รับการพิจารณาที่เชื่อถือได้โดยไม่ต้องทำตามขั้นตอนเพิ่มเติมเพื่อยืนยันพวกเขา


ขอบคุณ cmc ทุกจุดที่ดีและอาหารที่ยอดเยี่ยมสำหรับความคิด ฉันได้ใช้วิธีการโทเค็น / HMAC คล้ายกับที่คุณกล่าวไว้ข้างต้นแทนที่จะเป็นกลไกการตรวจสอบสิทธิ์S3 REST API
cantlin

หากคุณแคชโทเค็นบนเซิร์ฟเวอร์โดยพื้นฐานแล้วมันจะไม่เหมือนกับรหัสเซสชันเก่าที่ดีใช่หรือไม่? รหัสเซสชันมีอายุการใช้งานสั้นและยังแนบไปกับที่เก็บแคชที่รวดเร็ว (หากคุณใช้งาน) เพื่อหลีกเลี่ยงการกดปุ่ม DB ในทุกคำขอ True RESTful & stateless design ไม่ควรมีเซสชัน แต่ถ้าคุณใช้โทเค็นเป็น ID แล้วยังกดปุ่ม DB อยู่จะดีกว่าแค่ใช้รหัสเซสชันแทนหรือไม่? หรือคุณสามารถใช้โทเค็นเว็บ JSON ที่มีข้อมูลเข้ารหัสหรือเซ็นชื่อสำหรับข้อมูลเซสชันทั้งหมดสำหรับการออกแบบที่ไม่มีสถานะที่แท้จริง
JustAMartin

16

RESTful API ที่บริสุทธิ์ควรใช้คุณสมบัติมาตรฐานโปรโตคอลพื้นฐาน:

  1. สำหรับ HTTP RESTful API ควรสอดคล้องกับส่วนหัวมาตรฐาน HTTP ที่มีอยู่ การเพิ่มส่วนหัว HTTP ใหม่ละเมิดหลักการ REST อย่าสร้างวงล้อขึ้นมาใหม่ใช้คุณลักษณะมาตรฐานทั้งหมดในมาตรฐาน HTTP / 1.1 รวมถึงรหัสตอบสนองสถานะส่วนหัวและอื่น ๆ บริการเว็บ RESTFul ควรใช้ประโยชน์และพึ่งพามาตรฐาน HTTP

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

บรรทัดล่าง: สำหรับวัตถุประสงค์ในการตรวจสอบสิทธิ์ / การอนุญาตคุณควรใช้ส่วนหัวการอนุญาตมาตรฐาน HTTP นั่นคือคุณควรเพิ่มส่วนหัวการอนุญาต / การพิสูจน์ตัวตน HTTP ในแต่ละคำขอที่ตามมาซึ่งจำเป็นต้องได้รับการพิสูจน์ตัวตน REST API ควรเป็นไปตามมาตรฐาน HTTP Authentication Scheme ลักษณะเฉพาะของวิธีการจัดรูปแบบส่วนหัวนี้ถูกกำหนดไว้ในมาตรฐาน RFC 2616 HTTP 1.1 - ส่วนที่ 14.8 การให้สิทธิ์ RFC 2616 และใน RFC 2617 HTTP Authentication: Basic and Digest Access Authentication .

ฉันได้พัฒนาบริการ RESTful สำหรับแอปพลิเคชัน Cisco Prime Performance Manager การค้นหาของ Google สำหรับเอกสาร REST API ที่ผมเขียนโปรแกรมที่สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการปฏิบัติสงบ API ที่นี่ ในการใช้งานนั้นฉันได้เลือกใช้รูปแบบการอนุญาต "พื้นฐาน" ของ HTTP - ตรวจสอบเวอร์ชัน 1.5 หรือสูงกว่าของเอกสาร REST API นั้นและค้นหาการอนุญาตในเอกสาร


8
"การเพิ่มส่วนหัว HTTP ใหม่ละเมิดหลักการ REST"อย่างไร? และถ้าคุณอยู่ที่นั่นคุณอาจจะกรุณาอธิบายว่าอะไรคือความแตกต่าง (เกี่ยวกับหลักการ) ระหว่างรหัสผ่านที่หมดอายุหลังจากช่วงเวลาหนึ่งและโทเค็นที่หมดอายุหลังจากช่วงเวลาหนึ่ง
ที่ดีกว่า

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

1
ฉันคิดว่าคุณควรกระตุ้นว่าทำไมต้องดำเนินการกับ RESTful ผ่านการรับรองความถูกต้องพื้นฐานซึ่งเป็นส่วนหนึ่งของคำถามเดิม บางทีคุณอาจเชื่อมโยงไปยังตัวอย่างที่ดีที่มีโค้ดรวมอยู่ด้วยก็ได้ ในฐานะผู้เริ่มต้นในเรื่องนี้ดูเหมือนว่าทฤษฎีจะชัดเจนเพียงพอพร้อมแหล่งข้อมูลที่ดีมากมาย แต่วิธีการนำไปใช้นั้นไม่เป็นเช่นนั้นและตัวอย่างจะซับซ้อน ฉันพบว่ามันน่าหงุดหงิดที่ดูเหมือนว่าจะต้องใช้การเข้ารหัสแบบกำหนดเองเพื่อใช้ในเวลาที่เหมาะสมซึ่งมีการทำหลายพันครั้ง
JPK

13
-1 "กลอุบายใด ๆ เช่นการพิสูจน์ตัวตนโดยใช้โทเค็นที่พยายามจดจำสถานะของคำขอ REST ก่อนหน้านี้บนเซิร์ฟเวอร์ที่ละเมิดหลักการ REST" รับรองความถูกต้องตามโทเค็นมีอะไรจะทำอย่างไรกับสถานะของการร้องขอ REST ก่อนหน้าและไม่ละเมิดไร้สัญชาติของส่วนที่เหลือ
Kerem Baydoğan

1
ดังนั้นตามนี้ JSON Web Tokens เป็นการละเมิด REST เนื่องจากสามารถจัดเก็บสถานะของผู้ใช้ (การอ้างสิทธิ์) ได้? อย่างไรก็ตามฉันชอบที่จะละเมิด REST และใช้รหัสเซสชันเก่าที่ดีเป็น "โทเค็น" แต่การตรวจสอบความถูกต้องเบื้องต้นจะดำเนินการด้วยชื่อผู้ใช้ + รหัสผ่านลงนามหรือเข้ารหัสโดยใช้ความลับที่ใช้ร่วมกันและการประทับเวลาที่สั้นมาก (ดังนั้นจึงล้มเหลวหากมีใครพยายามเล่นซ้ำ ที่). ในแอปพลิเคชัน "enterprise-ish" เป็นการยากที่จะทิ้งสิทธิประโยชน์ของเซสชัน (หลีกเลี่ยงการกดฐานข้อมูลสำหรับข้อมูลบางอย่างที่จำเป็นในเกือบทุกคำขอ) ดังนั้นบางครั้งเราจึงต้องสละความไร้สัญชาติที่แท้จริง
JustAMartin

2

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

ชื่อผู้ใช้ / รหัสผ่าน (ส่งไปที่ส่วนหัวการอนุญาต) มักจะยังคงอยู่ในฐานข้อมูลโดยมีจุดประสงค์เพื่อระบุตัวผู้ใช้ บางครั้งผู้ใช้อาจหมายถึงแอปพลิเคชันอื่น อย่างไรก็ตามชื่อผู้ใช้ / รหัสผ่านไม่เคยมีไว้เพื่อระบุตัวแทนผู้ใช้เว็บไคลเอ็นต์ที่เฉพาะเจาะจง การสนทนาระหว่างเว็บเอเจนต์และเซิร์ฟเวอร์โดยใช้ชื่อผู้ใช้ / รหัสผ่านในส่วนหัวการอนุญาต (ตามหลังการอนุญาตพื้นฐานของ HTTP) เป็นแบบ STATELESS เนื่องจากส่วนหน้าเว็บเซิร์ฟเวอร์ไม่ได้สร้างหรือรักษาข้อมูล STATE ใด ๆในนามของตัวแทนผู้ใช้เว็บไคลเอ็นต์ใด ๆ และจากความเข้าใจของฉันเกี่ยวกับ REST โปรโตคอลระบุอย่างชัดเจนว่าการสนทนาระหว่างไคลเอนต์และเซิร์ฟเวอร์ควรเป็นแบบ STATELESS ดังนั้นหากเราต้องการมีบริการ RESTful ที่แท้จริงเราควรใช้ชื่อผู้ใช้ / รหัสผ่าน (อ้างถึง RFC ที่กล่าวถึงในโพสต์ก่อนหน้าของฉัน) ในส่วนหัวการอนุญาตสำหรับการโทรทุกครั้งไม่ใช่โทเค็นประเภทความรู้สึก (เช่นโทเค็นเซสชันที่สร้างในเว็บเซิร์ฟเวอร์ , โทเค็น OAuth ที่สร้างในเซิร์ฟเวอร์การอนุญาตและอื่น ๆ )

ฉันเข้าใจว่าผู้ให้บริการ REST หลายรายใช้โทเค็นเช่น OAuth1 หรือ OAuth2 เพื่อส่งต่อเป็น "Authorization: Bearer" ในส่วนหัว HTTP อย่างไรก็ตามสำหรับฉันแล้วดูเหมือนว่าการใช้โทเค็นเหล่านั้นสำหรับบริการ RESTful จะละเมิดความหมายที่แท้จริงที่ REST ยอมรับ เนื่องจากโทเค็นเหล่านั้นเป็นข้อมูลชั่วคราวที่สร้าง / ดูแลบนฝั่งเซิร์ฟเวอร์เพื่อระบุตัวแทนผู้ใช้เว็บไคลเอ็นต์เฉพาะสำหรับระยะเวลาที่ถูกต้องของการสนทนาเว็บไคลเอ็นต์ / เซิร์ฟเวอร์นั้น ดังนั้นบริการใด ๆ ที่ใช้โทเค็น OAuth1 / 2 เหล่านั้นไม่ควรถูกเรียกว่า REST หากเราต้องการยึดติดกับความหมายจริงของโปรโตคอล STATELESS

รูเบนส์

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