เอา body กลับเข้าสัญญา แล้วพบว่าต้องลงมือสองที่
บทที่ 1 วัดได้ว่าคำตอบที่ปฏิเสธผู้เรียกมี body ว่างเปล่า และ advice ของเราเอื้อมไม่ถึง · สี่บทที่ผ่านมาไม่มีบทไหนแก้ข้อนั้นเลย · บทนี้แก้
filter chain มีสองทางออก และแต่ละทางมีตัวเขียนคำตอบของตัวเอง
หัวข้อที่มีชื่อว่า “filter chain มีสองทางออก และแต่ละทางมีตัวเขียนคำตอบของตัวเอง”ถ้า advice อยู่ผิดชั้น ก็ต้องเขียนของที่อยู่ในชั้นที่ถูก
filter chain มีสองทางออกสำหรับคำขอที่ไม่ผ่าน และแต่ละทางมีตัวเขียนคำตอบของตัวเอง
- AuthenticationEntryPointAuthenticationEntryPointตัวที่เขียนคำตอบให้ผู้เรียกที่ยังไม่ได้ยืนยันตัว · เป็นที่เดียวที่เราเติม body ให้ 401 ได้ เพราะ advice เอื้อมไม่ถึง · สำหรับผู้เรียกที่ยังไม่ยืนยันตัว
- AccessDeniedHandlerAccessDeniedHandlerตัวที่เขียนคำตอบให้ผู้เรียกที่ยืนยันตัวแล้วแต่ไม่มีสิทธิ์ · คู่กับตัวบนคือสองที่ที่ต้องเขียนเอง ถ้าอยากให้ body ของ security กลับเข้าสัญญาเดิม · สำหรับผู้เรียกที่ยืนยันตัวแล้วแต่สิทธิ์ไม่พอ
บทที่ 3 อธิบายไว้แล้วว่าใครเป็นคนเลือกว่าคำขอจะไปทางไหน · บทนี้เขียนสิ่งที่รออยู่ปลายทาง
เขียนทั้งสองตัว ให้คืน body รูปเดียวกัน
หัวข้อที่มีชื่อว่า “เขียนทั้งสองตัว ให้คืน body รูปเดียวกัน”รูปเดียวกันจริงๆ ไม่ใช่คล้ายกัน
private fun write(response: HttpServletResponse, request: HttpServletRequest, code: Int, title: String, writtenBy: String) { response.status = code response.contentType = "application/json" response.writer.write( """{"type":"about:blank","title":"$title","status":$code,""" + """"instance":"${request.requestURI}","kaenWrittenBy":"$writtenBy"}""" )}kaenWrittenBy ไม่ใช่ส่วนหนึ่งของสัญญา · มันคือ marker ที่ใส่ไว้เพื่อพิสูจน์ว่า
ใครเป็นคนเขียน body ใบนั้น · บทนี้ใช้มันตอบคำถามที่รหัสตอบกลับตอบไม่ได้
http.exceptionHandling { it.authenticationEntryPoint(ContractEntryPoint()) it.accessDeniedHandler(ContractDeniedHandler())}สามช่องที่เคยได้ศูนย์คีย์ ได้ห้าคีย์เท่ากันหมด
หัวข้อที่มีชื่อว่า “สามช่องที่เคยได้ศูนย์คีย์ ได้ห้าคีย์เท่ากันหมด”สามช่องที่เคยได้ศูนย์คีย์ ได้ห้าคีย์เท่ากันหมด
| ช่อง | ก่อน | หลัง | ใครเขียน |
|---|---|---|---|
GET /boom ที่โยน exception | 500 · 5 คีย์ | 500 · 5 คีย์ | advice เหมือนเดิม |
GET /open ที่ permitAll ไว้ | 200 · 1 คีย์ | 200 · 1 คีย์ | controller |
GET /admin ไม่ส่งรหัสผ่าน | 401 · 0 | 401 · 5 | entry-point |
GET /admin รหัสผ่านผิด | 401 · 0 | 401 · 5 | entry-point |
GET /admin สิทธิ์ไม่พอ | 403 · 0 | 403 · 5 | denied-handler |
ชุดคีย์ของสามช่องล่างเท่ากันทั้งสามช่อง คือ
instance kaenWrittenBy status title type
รหัสตอบกลับไม่ขยับสักช่อง · สิ่งที่เปลี่ยนคือมีเนื้อความให้ผู้เรียกอ่าน
marker บอกสิ่งที่รหัสตอบกลับบอกไม่ได้
หัวข้อที่มีชื่อว่า “marker บอกสิ่งที่รหัสตอบกลับบอกไม่ได้”สัญญาหนึ่งข้อ แต่มีผู้เขียนสองราย
ช่องขวาสุดของตารางคือคำตอบของบทนี้ · สองช่องบนถูกเขียนโดยตัวหนึ่ง ช่องล่างถูกเขียนโดยอีกตัวหนึ่ง · สองตัวนี้ไม่แชร์อะไรกันเลย
คนที่รู้ว่า body หายไป มักเขียน AuthenticationEntryPoint ตัวเดียวแล้วจบ
เพราะ 401 คือสิ่งที่เขาเห็นบ่อยที่สุด
ผลคือ 401 กลับมามีเนื้อความ ส่วน 403 ยังว่างอยู่ · และไม่มีอะไรเตือน
เพราะทั้งสองทางไม่ได้ผ่านที่เดียวกัน
ลงมือที่เดียวแล้วเห็นครึ่งเดียวดีขึ้น อ่านเหมือนแก้เสร็จแล้ว
ช่องควบคุมยังต้องเป็นของเดิม
หัวข้อที่มีชื่อว่า “ช่องควบคุมยังต้องเป็นของเดิม”ของใหม่ต้องไม่ไปแย่งปลายทางที่ไม่ใช่ของมัน
แถวแรกของตารางคือ GET /boom ซึ่งยังได้ 500 พร้อมคีย์ครบห้าตัวและ marker ของ advice
เหมือนเดิมทุกช่อง
ถ้าแถวนั้นเปลี่ยน แปลว่าตัวเขียนใหม่ไปรับเรื่องที่ไม่ใช่ของมัน แล้วเราจะกลายเป็นคนที่ กลบ exception จริงด้วยคำตอบของ security
สิ่งที่บทนี้ทิ้งไว้
หัวข้อที่มีชื่อว่า “สิ่งที่บทนี้ทิ้งไว้”body กลับมาแล้ว แต่ยังไม่รู้ว่าระบบเชื่ออะไรเกี่ยวกับผู้เรียกคนนั้น
ทุกกฎที่เขียนมาถึงตอนนี้อ้างถึง role · บทถัดไปเปิดดูว่าสิ่งที่ระบบถืออยู่จริง ตรงกับที่เราประกาศไว้หรือเปล่า
- Spring Boot 4.1.1 · Spring Security 7.1.1 · Kotlin 2.4.10 · JDK 25
- รันซ้ำเองได้ด้วย
npm run verify:clinic-security· ต้องสั่งscripts/clinic-security/up.shก่อน เพราะทรงของบทที่ 7 ต้องใช้ฐานข้อมูล - ทุกตัวเลขในตารางถูกตรึงไว้ที่
src/data/perf/clinic-security.tsและtest/course-clinic-security.test.tsผูกตัวเลขในบทเข้ากับ file นั้น - Spring Security — Exception Handling (เข้าถึง 2026-09-02) — บทบาทของสองตัวเขียนคำตอบ
เช็กความเข้าใจ — บทที่ 5
ข้อ 1 / 3ทำไมการเขียน `AuthenticationEntryPoint` ตัวเดียวถึงไม่พอ