05 · การตรวจสอบโทเค็นและความปลอดภัย
ไม่ว่าจะแบบ Push หรือ Pull สุดท้ายนักพัฒนาจะได้ wsa(HS256 JWT)หนึ่งใบ บทความนี้เป็นสิ่งที่ผู้เชื่อมต่อทุกฝ่ายต้องอ่าน — ตรวจสอบไม่รัดกุม = ตัวตนถูกปลอมแปลงได้
รายการตรวจสอบบังคับ
หลังได้ wsa แล้ว ในบริการแบ็กเอนด์ของแอปส่วนขยายให้ตรวจสอบตามลำดับ(verifyWsa มีครบในตัวแล้ว):
- ลายเซ็น: ใช้คีย์ลับที่ได้จากการแจกจ่าย/ลงทะเบียนของแพลตฟอร์มตรวจสอบด้วย HS256 ล้มเหลว → ปฏิเสธ
exp: เวลาปัจจุบัน ≤exp(รวม leeway เล็กน้อย)expต้องมีอยู่ — โทเค็นที่ไม่มีexpควรปฏิเสธทันที(ไม่เช่นนั้นเท่ากับไม่มีวันหมดอายุ)iss: ต้องเท่ากับgptbots-workspaceaud: ต้องเท่ากับ host ของแอปส่วนขยายเป้าหมาย นี่คือแนวป้องกันสำคัญในการกัน「โทเค็นถูกขโมยไปใช้กับอีกแอปหนึ่ง」ห้ามละเว้นiat/nbf(หากมี): ต้องไม่อยู่ในอนาคต(เกิน leeway)เพื่อป้องกันโทเค็นที่「ตั้งเวลาออกไว้ในอนาคตอันไกล」ใช้งานได้ยาวนาน- แยกผู้เช่าตาม
workspace_id: หากแอปส่วนขยายทำการแยกหลายพื้นที่ทำงาน ให้จัดคำขออยู่ภายใต้workspace_idนั้น ห้ามเข้าถึงข้ามผู้เช่า - หลัง Landing ลบ
wsa/codeออกจาก URL ทันที:history.replaceState, เลี่ยงกรณีผู้ใช้คัดลอก URL แล้วแชร์โทเค็นออกไปด้วย - สร้างเซสชันของตัวเอง: แลกเป็น session/cookie ของแอปส่วนขยายเอง คำขอถัดไปไม่ต้องพึ่ง
wsaอีก — มันหมดอายุใน 5 นาที
verifyWsaของ SDK ทางการบังคับให้expต้องมีอยู่, ตรวจสอบการเลื่อนไปอนาคตของiat/nbf, และทำการเปรียบเทียบลายเซ็นแบบเวลาคงที่พร้อม whitelist อัลกอริทึม(ค่าเริ่มต้นเฉพาะHS256เพื่อกันการโจมตีสับสนalg)ใช้มันก็จะครอบคลุมข้อ 1–5 โดยอัตโนมัติ
แนะนำให้ใช้ SDK ตรวจสอบ
import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';
try {
const id = verifyWsa(wsa, {
secret: process.env.EXTENSION_APP_SECRET,
audience: 'app.example.com',
});
// id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? }
} catch (e) {
if (e instanceof WsaVerificationError) {
// e.code: InvalidToken | InvalidSignature | Expired | NotYetValid
// | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm
}
}
ตัวเลือกครบถ้วนและรหัสข้อผิดพลาดดูที่ SDK-API-อ้างอิง
การตรวจสอบเมื่อไม่ใช้ SDK(หลายภาษา)
wsa เป็น HS256 JWT มาตรฐาน ไลบรารี JWT ใดก็ตรวจสอบได้ โปรดเปิดการตรวจสอบ iss / aud อย่างชัดเจน และล็อก algorithms=['HS256']
Java(auth0 java-jwt)
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET))
.withIssuer("gptbots-workspace")
.withAudience("app.example.com")
.acceptLeeway(30) // ผ่อนผันการคลาดเคลื่อนของนาฬิกา 30s
.build();
DecodedJWT jwt = verifier.verify(wsaParam);
String userId = jwt.getSubject();
String workspaceId = jwt.getClaim("workspace_id").asString();
String role = jwt.getClaim("role").asString();
String username = jwt.getClaim("username").asString(); // อาจเป็น null
String email = jwt.getClaim("email").asString(); // อาจเป็น null
Node.js(jsonwebtoken)
const jwt = require('jsonwebtoken');
const payload = jwt.verify(wsaParam, SHARED_SECRET, {
algorithms: ['HS256'],
issuer: 'gptbots-workspace',
audience: 'app.example.com',
clockTolerance: 30,
});
const { sub: userId, workspace_id, role, username, email, avatar } = payload;
Python(PyJWT)
import jwt
payload = jwt.decode(
wsa_param,
SHARED_SECRET,
algorithms=["HS256"],
issuer="gptbots-workspace",
audience="app.example.com",
leeway=30,
)
user_id = payload["sub"]
workspace_id = payload["workspace_id"]
role = payload["role"]
เตือนเรื่องการเข้ารหัสคีย์ลับ: HS256 ใช้สตริงคีย์ลับเป็นไบต์ UTF-8 โดยตรงเป็น HMAC key คีย์ลับ per-app ของ Tier 2 มีรูปแบบ
wext_+64 hex โปรดส่งเข้าไปเป็นสตริงตามเดิมเป็นคีย์ลับ(อย่า hex decode อีก)ให้สอดคล้องกับฝั่งลงลายเซ็นของแพลตฟอร์ม
ข้อกำหนดด้านความปลอดภัย(ปฏิบัติทีละข้อ)
- คีย์ลับคือโมเดลความปลอดภัยทั้งหมด ปัจจุบัน HS256 เป็นคีย์ลับแบบสมมาตร: เมื่อรั่วไหล ใครก็สามารถปลอมแปลงตัวตนของผู้ใช้พื้นที่ทำงานใดก็ได้เพื่อโจมตีแอปส่วนขยายที่เชื่อมต่อ เก็บไว้ที่แบ็กเอนด์เท่านั้น(ตัวแปรสภาพแวดล้อม/KMS)ห้ามเขียนลงในโค้ดฟรอนต์เอนด์, repo git, log, ค่าคอนฟิกฝั่งไคลเอนต์เด็ดขาด
- พื้นที่เปิดเผยของ JWT ใน URL(เฉพาะแบบ Push): query string จะถูกบันทึกในประวัติเบราว์เซอร์, access log ของเว็บเซิร์ฟเวอร์, log แคช CDN, บันทึก Referer ถึงแม้จะถูกบันทึกไว้ หากถูกดักได้ภายใน 5 นาทีก็ยังใช้ประโยชน์ได้ หลัง Landing ให้
history.replaceStateลบทันที(consumeHandoffทำให้เป็นค่าเริ่มต้นแล้ว)แบบ Pull(M-Auth)โดยธรรมชาติไม่ใส่wsaลงใน URL จึงปลอดภัยกว่า - อย่าส่งผ่าน
wsaไปยังทรัพยากรย่อย หลังแลกเป็นเซสชันของตัวเองแล้ว คำขอ XHR/fetch/img ถัดไปทั้งหมดต้องไม่พกwsaดั้งเดิมอีก ไม่เช่นนั้นมันจะปรากฏใน Referer ของทุกทรัพยากรย่อย - ซิงค์นาฬิกา HS256 ตัดสิน
expอย่างเข้มงวด นาฬิกาเซิร์ฟเวอร์ต้องซิงค์ NTP; ในตัวอย่างleeway30s ผ่อนผันการเลื่อนเล็กน้อย อย่าขยายไปถึงระดับนาที roleใช้เป็นการแมปสิทธิ์เริ่มต้นเท่านั้น มันสะท้อนเพียงบทบาทของผู้ใช้ในพื้นที่ทำงานนั้น ไม่ได้แทนสิทธิ์ภายในแอปของคุณ; แอปของคุณดูแลโมเดลสิทธิ์ของตัวเอง- แยกผู้เช่าหลายราย ใช้
workspace_idเป็นคีย์แยกข้อมูลเสมอ เพื่อกันไม่ให้ผู้ใช้ของพื้นที่ทำงาน A อ่านข้อมูลของ B ได้ - การรับมือฉุกเฉินเรื่องหมุนเวียน/เพิกถอนคีย์ลับ: ในการจัดการพื้นที่ ให้「หมุนเวียนคีย์ลับ」ของแอป จะสร้างคีย์ลับใหม่และแสดงเพียงครั้งเดียว จากนั้นใช้คีย์ลับใหม่ตั้งค่าแบ็กเอนด์ของคุณอีกครั้ง
5. ขอบเขตความน่าเชื่อถือของฟรอนต์เอนด์(สำคัญ)
- ฟรอนต์เอนด์ทำเพียง placeholder แสดงผล ไม่ตัดสินใจเรื่องสิทธิ์ ฟรอนต์เอนด์สามารถ base64 ถอด payload ของ JWT ได้ แต่ไม่มีคีย์ลับก็ตรวจลายเซ็นไม่ได้ — ใครก็สร้าง payload ที่「ดูเหมือนถูก」ได้
- การตัดสินทุกอย่างว่า「คนนี้เป็นใคร ทำสิ่งนี้ได้ไหม」ต้องยึดผลของ
verifyWsaที่แบ็กเอนด์ของแอปส่วนขยายเป็นหลัก - ดังนั้นกลยุทธ์มาตรฐานคือ: ฟรอนต์เอนด์ส่ง
wsa/codeให้แบ็กเอนด์ของแอปส่วนขยาย → แบ็กเอนด์ตรวจสอบ → แบ็กเอนด์สร้างเซสชัน → ฟรอนต์เอนด์เชื่อเฉพาะเซสชันนี้
ขั้นถัดไป: SDK-API-อ้างอิง เพื่อดู API ครบถ้วน; เมื่อพบปัญหาใด ๆ โปรดดูที่ คำถามที่พบบ่อยและการแก้ปัญหา
