ข้าม​ไป​ยัง​เนื้อหา

สิทธิ์​ที่​ระบบ​ถือ​อยู่​จริง ไม่ใช่​สิ่ง​ที่​เรา​ประกาศ​ไว้

ห้า​บท​ที่​ผ่าน​มา​เขียน​กฎ​ที่​อ้าง​ถึง role อยู่​ตลอด · แต่​ไม่มี​บท​ไหน​เปิด​ดู​เลย​ว่า​สิ่ง​ที่​ระบบ​ถือ​อยู่ หน้าตา​เป็น​อย่างไร

บท​นี้​เพิ่ม​ปลาย​ทาง​ที่​คืน​สิ่ง​นั้น​ออก​มา​ตรงๆ

ผู้​ใช้​หนึ่ง​คน role หนึ่ง​ชื่อ

ผู้​ใช้​คน​เดียว​ใน​ระบบ ตั้งแต่​บท​ที่ 1
User.withUsername("kaen").password("{noop}pw-for-probe-only").roles("USER").build()

หนึ่ง​ชื่อ · USER

สอง​รายการ และ​ไม่มี​รายการ​ไหน​สะกด​ว่า USER

คำ​ตอบ​ของ GET /whoami
{
"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 /byrolehasRole("USER")200
GET /byauthorityhasAuthority("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')` แล้วระบบถือสิทธิ์อะไรบ้าง