คำตอบด้านล่างเกี่ยวข้องกับSigned Cookiesเป็นหลักซึ่งเป็นการนำแนวคิดของเซสชันไปใช้ (ตามที่ใช้ในเว็บแอปพลิเคชัน) Flask มีทั้งคุกกี้ปกติ (ไม่ได้ลงชื่อ) (ผ่านrequest.cookies
และresponse.set_cookie()
) และคุกกี้ที่มีลายเซ็น (ผ่านflask.session
) คำตอบมีสองส่วนส่วนแรกอธิบายถึงวิธีสร้างคุกกี้ที่มีลายเซ็นและส่วนที่สองจะถูกนำเสนอในรูปแบบของ QA ที่กล่าวถึงแง่มุมต่างๆของโครงการ ไวยากรณ์ที่ใช้สำหรับตัวอย่างคือ Python3 แต่แนวคิดนี้ยังใช้กับเวอร์ชันก่อนหน้าด้วย
อะไรคือSECRET_KEY
(หรือสร้างคุกกี้ที่มีลายเซ็น) คืออะไร?
การเซ็นชื่อคุกกี้เป็นมาตรการป้องกันการปลอมแปลงคุกกี้ ในระหว่างขั้นตอนการเซ็นชื่อคุกกี้SECRET_KEY
จะมีการใช้คุกกี้ในลักษณะที่คล้ายกับการใช้ "เกลือ" เพื่อโกงรหัสผ่านก่อนที่จะแฮช นี่คือคำอธิบายที่เรียบง่าย (อย่างสิ้นเชิง) ของแนวคิด รหัสในตัวอย่างมีวัตถุประสงค์เพื่อเป็นภาพประกอบ หลายขั้นตอนถูกละไว้และไม่ใช่ฟังก์ชันทั้งหมดที่มีอยู่จริง เป้าหมายคือเพื่อให้เข้าใจแนวคิดทั่วไปการนำไปใช้จริงจะมีส่วนเกี่ยวข้องมากขึ้นเล็กน้อย นอกจากนี้โปรดทราบว่า Flask ทำส่วนใหญ่ให้คุณอยู่เบื้องหลัง ดังนั้นนอกเหนือจากการตั้งค่าให้กับคุกกี้ของคุณ (ผ่านทาง API ของเซสชัน) และการให้ a SECRET_KEY
แล้วไม่เพียง แต่แนะนำให้ติดตั้งใหม่ด้วยตัวเองเท่านั้น แต่ไม่จำเป็นต้องทำเช่นนั้น
ลายเซ็นคุกกี้ของชายผู้น่าสงสาร
ก่อนส่งการตอบกลับไปยังเบราว์เซอร์:
(1) SECRET_KEY
ก่อตั้งa ครั้งแรก ควรทราบเฉพาะแอปพลิเคชันเท่านั้นและควรรักษาไว้ให้คงที่ตลอดวงจรชีวิตของแอปพลิเคชันรวมถึงการรีสตาร์ทแอปพลิเคชัน
# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')
(2) สร้างคุกกี้
>>> cookie = make_cookie(
... name='_profile',
... content='uid=382|membership=regular',
... ...
... expires='July 1 2030...'
... )
>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
...
...
expires: July 1 2030, 1:20:40 AM UTC
(3) เพื่อสร้างลายเซ็นต่อท้าย (หรือนำหน้า) SECRET_KEY
ไปที่สตริงไบต์ของคุกกี้จากนั้นสร้างแฮชจากชุดค่าผสมนั้น
# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....
(4) ติดลายเซ็นที่ปลายด้านหนึ่งของcontent
ช่องของคุกกี้ดั้งเดิม
# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9... <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
และนั่นคือสิ่งที่ส่งไปยังลูกค้า
# add cookie to response
>>> response.set_cookie(cookie)
# send to browser -->
เมื่อได้รับคุกกี้จากเบราว์เซอร์:
(5) เมื่อเบราว์เซอร์ส่งคืนคุกกี้นี้กลับไปที่เซิร์ฟเวอร์ให้ดึงลายเซ็นออกจากช่องของคุกกี้content
เพื่อดึงคุกกี้เดิมกลับคืนมา
# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)
(6) ใช้คุกกี้ดั้งเดิมกับแอปพลิเคชันSECRET_KEY
เพื่อคำนวณลายเซ็นใหม่โดยใช้วิธีการเดียวกับในขั้นตอนที่ 3
# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
(7) เปรียบเทียบผลการคำนวณกับลายเซ็นก่อนหน้านี้ที่โผล่ออกมาจากคุกกี้ที่เพิ่งได้รับ หากตรงกันเราทราบว่าคุกกี้ไม่ได้ถูกรบกวน แต่ถ้าเพิ่มแค่ช่องว่างในคุกกี้ลายเซ็นจะไม่ตรงกัน
# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature
(8) หากไม่ตรงกันคุณสามารถตอบสนองด้วยการดำเนินการจำนวนเท่าใดก็ได้บันทึกเหตุการณ์ทิ้งคุกกี้ออกใหม่เปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ ฯลฯ
>>> if not good_cookie:
... security_log(cookie)
รหัสการตรวจสอบข้อความตามแฮช (HMAC)
ประเภทของลายเซ็นที่สร้างขึ้นดังกล่าวข้างต้นที่ต้องใช้คีย์ลับเพื่อความสมบูรณ์ของเนื้อหาบางส่วนที่เรียกว่าในการเข้ารหัสรับรองความถูกต้องรหัสข้อความหรือMAC
ฉันได้ระบุไว้ก่อนหน้านี้ว่าตัวอย่างข้างต้นเป็นการอธิบายแนวคิดนั้นมากเกินไปและไม่ใช่ความคิดที่ดีที่จะใช้การลงนามของคุณเอง นั่นเป็นเพราะอัลกอริทึมที่ใช้ในการเซ็นชื่อคุกกี้ใน Flask เรียกว่าHMACและมีส่วนเกี่ยวข้องมากกว่าขั้นตอนง่ายๆข้างต้นเล็กน้อย แนวคิดทั่วไปเหมือนกัน แต่เนื่องจากเหตุผลที่อยู่นอกเหนือขอบเขตของการสนทนานี้ชุดการคำนวณจึงซับซ้อนกว่าเล็กน้อย หากคุณยังคงสนใจในการประดิษฐ์ DIY ตามปกติ Python มีโมดูลบางอย่างที่จะช่วยคุณเริ่มต้น :) นี่คือบล็อกเริ่มต้น:
import hmac
import hashlib
def create_signature(secret_key, msg, digestmod=None):
if digestmod is None:
digestmod = hashlib.sha1
mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
return mac.digest()
documentaton สำหรับHMACและhashlib
"Demystification" ของSECRET_KEY
:)
"ลายเซ็น" ในบริบทนี้คืออะไร
เป็นวิธีการตรวจสอบให้แน่ใจว่าเนื้อหาบางส่วนไม่ได้ถูกแก้ไขโดยบุคคลอื่นที่ไม่ใช่บุคคลหรือหน่วยงานที่ได้รับอนุญาตให้ทำเช่นนั้น
รูปแบบลายเซ็นที่ง่ายที่สุดรูปแบบหนึ่งคือ " checksum " ซึ่งจะตรวจสอบว่าข้อมูลสองส่วนเหมือนกัน ตัวอย่างเช่นเมื่อติดตั้งซอฟต์แวร์จากแหล่งที่มาสิ่งสำคัญคือต้องยืนยันก่อนว่าสำเนาของซอร์สโค้ดของคุณเหมือนกับของผู้เขียน วิธีการทั่วไปในการทำเช่นนี้คือเรียกใช้ซอร์สผ่านฟังก์ชันแฮชการเข้ารหัสและเปรียบเทียบผลลัพธ์กับการตรวจสอบที่เผยแพร่บนโฮมเพจของโครงการ
ตัวอย่างเช่นสมมติว่าคุณกำลังจะดาวน์โหลดซอร์สของโปรเจ็กต์ในไฟล์ gzipped จากเว็บมิเรอร์ การตรวจสอบ SHA1 ที่เผยแพร่บนหน้าเว็บของโครงการคือ 'eb84e8da7ca23e9f83 ... '
# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....
แฮชทั้งสองเหมือนกันคุณรู้ว่าคุณมีสำเนาที่เหมือนกัน
คุกกี้คืออะไร?
การอภิปรายอย่างกว้างขวางเกี่ยวกับคุกกี้จะเกินขอบเขตของคำถามนี้ ฉันให้ภาพรวมที่นี่เนื่องจากความเข้าใจเพียงเล็กน้อยจะมีประโยชน์ในการทำความเข้าใจให้ดีขึ้นว่าเหตุใดจึงSECRET_KEY
มีประโยชน์ ฉันขอแนะนำให้คุณติดตามการอ่านส่วนบุคคลเกี่ยวกับคุกกี้ HTTP
แนวทางปฏิบัติทั่วไปในเว็บแอปพลิเคชันคือการใช้ไคลเอนต์ (เว็บเบราว์เซอร์) เป็นแคชที่มีน้ำหนักเบา คุกกี้เป็นหนึ่งในการนำแนวทางปฏิบัตินี้ไปใช้ โดยทั่วไปคุกกี้คือข้อมูลบางส่วนที่เพิ่มโดยเซิร์ฟเวอร์เพื่อตอบสนอง HTTP โดยใช้ส่วนหัว มันถูกเก็บไว้โดยเบราว์เซอร์ซึ่งจะส่งกลับไปยังเซิร์ฟเวอร์ในเวลาต่อมาเมื่อออกคำขอรวมถึงส่วนหัว HTTP ข้อมูลที่อยู่ในคุกกี้สามารถใช้เพื่อเลียนแบบสิ่งที่เรียกว่าสภาวะภาพลวงตาที่เซิร์ฟเวอร์กำลังรักษาการเชื่อมต่ออย่างต่อเนื่องกับไคลเอนต์ เฉพาะในกรณีนี้แทนที่จะใช้สายเพื่อให้การเชื่อมต่อ "คงอยู่" คุณเพียงแค่มีสแนปชอตสถานะของแอปพลิเคชันหลังจากที่จัดการคำขอของลูกค้าแล้ว ภาพรวมเหล่านี้จะดำเนินการไปมาระหว่างไคลเอนต์และเซิร์ฟเวอร์ เมื่อได้รับคำขอเซิร์ฟเวอร์จะอ่านเนื้อหาของคุกกี้ก่อนเพื่อสร้างบริบทของการสนทนากับไคลเอ็นต์อีกครั้ง จากนั้นจัดการคำขอภายในบริบทนั้นและก่อนที่จะส่งคืนการตอบกลับไปยังไคลเอ็นต์ให้อัปเดตคุกกี้ ด้วยเหตุนี้ภาพลวงตาของเซสชันที่กำลังดำเนินอยู่จึงยังคงอยู่
คุกกี้มีลักษณะอย่างไร?
คุกกี้ทั่วไปจะมีลักษณะดังนี้:
name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
คุกกี้เป็นเรื่องเล็กน้อยที่จะอ่านได้จากเบราว์เซอร์สมัยใหม่ ใน Firefox ไปที่การตั้งค่า> ความเป็นส่วนตัว> ประวัติ> ลบคุกกี้แต่ละรายการ
content
สนามเป็นส่วนใหญ่ที่เกี่ยวข้องกับการใช้งาน สาขาอื่น ๆ มีคำสั่งเมตาเป็นส่วนใหญ่เพื่อระบุขอบเขตต่างๆ
ทำไมต้องใช้คุกกี้เลย?
คำตอบสั้น ๆ คือประสิทธิภาพ การใช้คุกกี้ช่วยลดความจำเป็นในการค้นหาสิ่งต่างๆในที่เก็บข้อมูลต่างๆ (แคชหน่วยความจำไฟล์ฐานข้อมูล ฯลฯ ) ซึ่งจะช่วยเร่งความเร็วของแอปพลิเคชันเซิร์ฟเวอร์ โปรดทราบว่ายิ่งคุกกี้มีขนาดใหญ่ก็จะยิ่งมีน้ำหนักบรรทุกมากขึ้นในเครือข่ายดังนั้นสิ่งที่คุณบันทึกไว้ในการค้นหาฐานข้อมูลบนเซิร์ฟเวอร์คุณอาจสูญเสียผ่านเครือข่าย พิจารณาสิ่งที่จะรวมไว้ในคุกกี้ของคุณอย่างรอบคอบ
ทำไมต้องเซ็นชื่อคุกกี้?
คุกกี้ถูกใช้เพื่อเก็บข้อมูลทุกประเภทซึ่งบางส่วนอาจมีความละเอียดอ่อนมาก พวกเขายังไม่ปลอดภัยโดยธรรมชาติและกำหนดให้มีการใช้มาตรการป้องกันเสริมหลายประการเพื่อให้ถือว่าปลอดภัยไม่ว่าจะด้วยวิธีใดก็ตามสำหรับทั้งสองฝ่ายไคลเอ็นต์และเซิร์ฟเวอร์ การเซ็นชื่อคุกกี้เป็นการแก้ปัญหาโดยเฉพาะซึ่งสามารถปรับแต่งได้เพื่อพยายามหลอกแอปพลิเคชันเซิร์ฟเวอร์ มีมาตรการอื่น ๆ เพื่อลดช่องโหว่ประเภทอื่น ๆ เราขอแนะนำให้คุณอ่านเพิ่มเติมเกี่ยวกับคุกกี้
คุกกี้สามารถดัดแปลงได้อย่างไร?
คุกกี้อยู่บนไคลเอนต์ในรูปแบบข้อความและสามารถแก้ไขได้โดยไม่ต้องใช้ความพยายาม คุกกี้ที่แอปพลิเคชันเซิร์ฟเวอร์ของคุณได้รับอาจได้รับการแก้ไขด้วยเหตุผลหลายประการซึ่งบางอย่างอาจไม่ใช่ผู้บริสุทธิ์ ลองนึกภาพเว็บแอปพลิเคชันที่เก็บข้อมูลการอนุญาตเกี่ยวกับผู้ใช้บนคุกกี้และให้สิทธิ์ตามข้อมูลนั้น หากคุกกี้ไม่สามารถป้องกันคนจรจัดใคร ๆ ก็สามารถปรับเปลี่ยนเพื่อยกระดับสถานะจาก "role = visitor" เป็น "role = admin" ได้และแอปพลิเคชันจะไม่มีใครฉลาดกว่า
เหตุใดจึงSECRET_KEY
จำเป็นต้องลงนามในคุกกี้?
การตรวจสอบคุกกี้นั้นแตกต่างจากการยืนยันซอร์สโค้ดตามที่อธิบายไว้ก่อนหน้านี้เล็กน้อย ในกรณีของซอร์สโค้ดผู้แต่งดั้งเดิมคือผู้ดูแลผลประโยชน์และเจ้าของลายนิ้วมืออ้างอิง (การตรวจสอบ) ซึ่งจะถูกเก็บไว้ในที่สาธารณะ สิ่งที่คุณไม่เชื่อถือคือซอร์สโค้ด แต่คุณเชื่อถือลายเซ็นสาธารณะ ดังนั้นในการตรวจสอบสำเนาแหล่งที่มาของคุณคุณเพียงแค่ต้องการให้แฮชที่คำนวณได้ตรงกับแฮชสาธารณะ
ในกรณีของคุกกี้ SECRET_KEY
แต่ใบสมัครไม่ได้ติดตามของลายเซ็นนั้นจะติดตามการทำงานของมัน SECRET_KEY
เป็นลายนิ้วมืออ้างอิง คุกกี้เดินทางพร้อมลายเซ็นที่อ้างว่าถูกต้องตามกฎหมาย ความถูกต้องตามกฎหมายในที่นี้หมายถึงลายเซ็นที่ออกโดยเจ้าของคุกกี้นั่นคือแอปพลิเคชันและในกรณีนี้เป็นการอ้างว่าคุณไม่ไว้วางใจและคุณต้องตรวจสอบลายเซ็นเพื่อความถูกต้อง ในการทำเช่นนั้นคุณต้องรวมองค์ประกอบในลายเซ็นที่คุณเท่านั้นที่รู้จักนั่นคือไฟล์SECRET_KEY
. อาจมีคนเปลี่ยนคุกกี้ แต่เนื่องจากพวกเขาไม่มีส่วนผสมที่เป็นความลับในการคำนวณลายเซ็นที่ถูกต้องจึงไม่สามารถปลอมแปลงได้ ตามที่ระบุไว้ก่อนหน้านี้เล็กน้อยของการพิมพ์ลายนิ้วมือประเภทนี้โดยที่ด้านบนของการตรวจสอบจะมีคีย์ลับด้วย
แล้วเซสชันล่ะ?
เซสชันในการใช้งานแบบคลาสสิกคือคุกกี้ที่มีเฉพาะ ID ในcontent
ฟิลด์session_id
เท่านั้น จุดประสงค์ของเซสชันเหมือนกับคุกกี้ที่ลงชื่อนั่นคือเพื่อป้องกันการปลอมแปลงคุกกี้ เซสชันคลาสสิกมีวิธีการที่แตกต่างออกไป เมื่อได้รับคุกกี้เซสชันเซิร์ฟเวอร์จะใช้ ID เพื่อค้นหาข้อมูลเซสชันในที่จัดเก็บข้อมูลในเครื่องของตนเองซึ่งอาจเป็นฐานข้อมูลไฟล์หรือบางครั้งก็เป็นแคชในหน่วยความจำ โดยทั่วไปคุกกี้เซสชันจะถูกกำหนดให้หมดอายุเมื่อปิดเบราว์เซอร์ เนื่องจากขั้นตอนการค้นหาที่จัดเก็บในตัวเครื่องการใช้งานเซสชันนี้มักจะก่อให้เกิดผลกระทบด้านประสิทธิภาพ คุกกี้ที่มีลายเซ็นกลายเป็นทางเลือกที่ต้องการและนั่นคือวิธีการใช้งานเซสชันของ Flask กล่าวอีกนัยหนึ่งเซสชันของ Flask คือคุกกี้ที่ลงชื่อและใช้คุกกี้ที่ลงนามใน Flask เพียงแค่ใช้Session
API
ทำไมไม่เข้ารหัสคุกกี้ด้วยล่ะ?
บางครั้งเนื้อหาของคุกกี้ที่สามารถเข้ารหัสก่อนที่ยังมีการลงนาม สิ่งนี้จะทำได้หากถือว่าไวเกินกว่าที่จะมองเห็นได้จากเบราว์เซอร์ (การเข้ารหัสจะซ่อนเนื้อหา) อย่างไรก็ตามเพียงแค่เซ็นชื่อคุกกี้ก็สามารถตอบสนองความต้องการที่แตกต่างกันได้ซึ่งเป็นสิ่งที่ต้องการรักษาระดับการมองเห็นและความสามารถในการใช้งานคุกกี้บนเบราว์เซอร์ในขณะเดียวกันก็ป้องกันไม่ให้ถูกแทรกแซง
จะเกิดอะไรขึ้นถ้าฉันเปลี่ยนSECRET_KEY
?
โดยการเปลี่ยนSECRET_KEY
คุณทำให้คุกกี้ทั้งหมดที่เซ็นชื่อด้วยคีย์ก่อนหน้านี้ไม่ถูกต้อง เมื่อแอปพลิเคชันได้รับคำขอพร้อมคุกกี้ที่ลงชื่อไว้ก่อนหน้านี้แอปพลิเคชันSECRET_KEY
จะพยายามคำนวณลายเซ็นด้วยลายเซ็นใหม่SECRET_KEY
และลายเซ็นทั้งสองจะไม่ตรงกันคุกกี้นี้และข้อมูลทั้งหมดจะถูกปฏิเสธมันจะเหมือนกับว่า เบราว์เซอร์กำลังเชื่อมต่อกับเซิร์ฟเวอร์เป็นครั้งแรก ผู้ใช้จะออกจากระบบและคุกกี้เก่าของพวกเขาจะถูกลืมพร้อมกับสิ่งที่เก็บไว้ภายใน โปรดทราบว่าสิ่งนี้แตกต่างจากวิธีจัดการกับคุกกี้ที่หมดอายุ คุกกี้ที่หมดอายุอาจมีการต่ออายุสัญญาเช่าหากลายเซ็นของมันถูกตรวจสอบ ลายเซ็นที่ไม่ถูกต้องแสดงถึงคุกกี้ธรรมดาที่ไม่ถูกต้อง
ดังนั้นหากคุณไม่ต้องการทำให้คุกกี้ที่ลงชื่อทั้งหมดเป็นโมฆะให้พยายามคงSECRET_KEY
ค่าเดิมไว้เป็นระยะ
มีอะไรดีSECRET_KEY
?
คีย์ลับน่าจะเดายาก เอกสารประกอบเกี่ยวกับเซสชันมีสูตรที่ดีสำหรับการสร้างคีย์แบบสุ่ม:
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
คุณคัดลอกคีย์และวางลงในไฟล์การกำหนดค่าของคุณเป็นค่าSECRET_KEY
.
หากไม่ใช้คีย์ที่สร้างขึ้นแบบสุ่มคุณสามารถใช้คำตัวเลขและสัญลักษณ์ที่ซับซ้อนซึ่งอาจจัดเรียงเป็นประโยคที่คุณรู้จักเท่านั้นโดยเข้ารหัสในรูปแบบไบต์
ไม่ได้ตั้งค่าSECRET_KEY
โดยตรงกับฟังก์ชั่นที่สร้างคีย์ที่แตกต่างกันในแต่ละครั้งก็เรียกว่า ตัวอย่างเช่นอย่าทำสิ่งนี้:
# this is not good
SECRET_KEY = random_key_generator()
ทุกครั้งที่รีสตาร์ทแอปพลิเคชันของคุณจะได้รับคีย์ใหม่ซึ่งจะทำให้ก่อนหน้านี้ไม่ถูกต้อง
ให้เปิด python shell แบบโต้ตอบและเรียกใช้ฟังก์ชันเพื่อสร้างคีย์จากนั้นคัดลอกและวางลงใน config