สิทธิ์ที่ระบบถืออยู่จริง ไม่ใช่สิ่งที่เราประกาศไว้
ห้าบทที่ผ่านมาเขียนกฎที่อ้างถึง role อยู่ตลอด · แต่ไม่มีบทไหนเปิดดูเลยว่าสิ่งที่ระบบถืออยู่ หน้าตาเป็นอย่างไร
บทนี้เพิ่มปลายทางที่คืนสิ่งนั้นออกมาตรงๆ
ประกาศไว้แบบนี้
หัวข้อที่มีชื่อว่า “ประกาศไว้แบบนี้”ผู้ใช้หนึ่งคน role หนึ่งชื่อ
User.withUsername("kaen").password("{noop}pw-for-probe-only").roles("USER").build()หนึ่งชื่อ · USER
ระบบถืออยู่แบบนี้
หัวข้อที่มีชื่อว่า “ระบบถืออยู่แบบนี้”สองรายการ และไม่มีรายการไหนสะกดว่า
USER
{ "name": "kaen", "authorities": "FACTOR_PASSWORD,ROLE_USER"}AuthenticationAuthenticationสิ่งที่ระบบเชื่อเกี่ยวกับผู้เรียกคนนี้ หลังยืนยันตัวสำเร็จ · ประกอบด้วยชื่อผู้ใช้และรายการสิทธิ์ · สิ่งที่ไม่ได้อยู่ในนี้ ระบบไม่รู้ ต่อให้เราคิดว่าส่งมาแล้ว คือสิ่งที่ระบบเชื่อเกี่ยวกับผู้เรียกคนนี้ หลังยืนยันตัวสำเร็จ · ข้างในมีรายการของ GrantedAuthorityGrantedAuthorityหนึ่งสิทธิ์ที่ผู้เรียกถืออยู่ · เป็นข้อความล้วน ไม่ใช่ชนิดข้อมูลที่ compiler ตรวจให้ · พิมพ์ผิดหนึ่งตัวอักษรจึงกลายเป็นสิทธิ์คนละอันที่ไม่มีใครถือ ซึ่งเป็นข้อความล้วน
สองรายการนั้นมาคนละทาง
| สิทธิ์ | มาจาก |
|---|---|
ROLE_USER | สิ่งที่เราประกาศ บวกคำนำหน้าที่ระบบเติมให้ |
FACTOR_PASSWORD | วิธีที่ผู้เรียกยืนยันตัว ไม่ได้มาจากที่เราประกาศเลย |
FACTOR_PASSWORD ไม่ได้อยู่ในโปรเจกต์ของเราสักบรรทัด · มันมาจากการที่ผู้เรียก
ยืนยันตัวด้วยรหัสผ่าน
กฎที่เขียนว่า “ต้องมีสิทธิ์ครบตามรายการนี้เท่านั้น” จึงเปราะ เพราะรายการจริง มีของที่คุณไม่ได้ใส่ · และของนั้นเปลี่ยนได้เมื่อวิธียืนยันตัวเปลี่ยน
คำนำหน้าที่ถูกเติมให้เงียบ
หัวข้อที่มีชื่อว่า “คำนำหน้าที่ถูกเติมให้เงียบ”สองปลายทาง กฎต่างกันคำเดียว ผลตรงข้ามกัน
it.requestMatchers("/byrole").hasRole("USER")it.requestMatchers("/byauthority").hasAuthority("USER")อ่านแล้วเหมือนพูดเรื่องเดียวกัน · ผู้เรียกคนเดียวกันยิงทั้งสองที่
| ปลายทาง | กฎ | รหัส |
|---|---|---|
GET /byrole | hasRole("USER") | 200 |
GET /byauthority | hasAuthority("USER") | 403 |
ROLE_ prefixROLE_ prefixคำนำหน้าที่ `hasRole` เติมให้เอง แต่ `hasAuthority` ไม่เติม · ประกาศ role ว่า `ADMIN` แล้วเทียบด้วย `hasAuthority("ADMIN")` จึงไม่ตรงกัน ทั้งที่อ่านแล้วเหมือนตรง คือคำอธิบาย · hasRole เติม ROLE_ ให้เองก่อนเทียบ ส่วน hasAuthority
เทียบข้อความที่ให้มาตรงตัว · สิทธิ์จริงคือ ROLE_USER จึงตรงกับตัวแรก ไม่ตรงกับตัวหลัง
ทำไมข้อนี้ถึงกัดคนที่รู้เรื่องนี้แล้ว
หัวข้อที่มีชื่อว่า “ทำไมข้อนี้ถึงกัดคนที่รู้เรื่องนี้แล้ว”ความผิดพลาดไม่ได้อยู่ที่การไม่รู้ มันอยู่ที่การผสมสองรูปในระบบเดียว
คนที่รู้กฎข้อนี้ยังพลาดได้ เพราะโปรเจกต์จริงมักมีทั้งสองรูปปนกัน
· บางที่ประกาศด้วย roles(...) บางที่ประกาศด้วย authorities(...)
· แล้วกฎที่เขียนทีหลังก็เลือกไม่ถูกว่าจะใช้ตัวไหน
GrantedAuthority เป็นข้อความล้วน ไม่ใช่ชนิดข้อมูลที่ compiler ตรวจให้
· พิมพ์ ROLE_ADMIM แทน ROLE_ADMIN แล้วไม่มีอะไรแดง · มันแค่กลายเป็นสิทธิ์คนละอัน
ที่ไม่มีใครถือ และกฎนั้นก็ปฏิเสธทุกคนตลอดไป
สิ่งที่บทนี้ทิ้งไว้
หัวข้อที่มีชื่อว่า “สิ่งที่บทนี้ทิ้งไว้”อ่านสิ่งที่ระบบเชื่อเป็นแล้ว ยังไม่ได้ถามว่ากฎระดับ method ทำงานจริงไหม
กฎทุกข้อจนถึงบทนี้อยู่ที่ระดับเส้นทาง · บทถัดไปย้ายกฎเข้าไปอยู่กับ method แล้ววัดสามชั้น คือกฎนั้นทำงานจริงไหม คำตอบที่ผู้เรียกได้เปลี่ยนไปอย่างไร และเมื่อทุกกฎผ่านแล้ว เขาเห็นแถวของใคร
- 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 — Authorities (เข้าถึง 2026-09-02) — ความต่างระหว่าง role กับ authority
เช็กความเข้าใจ — บทที่ 6
ข้อ 1 / 3ประกาศผู้ใช้ด้วย `roles('USER')` แล้วระบบถือสิทธิ์อะไรบ้าง