วิธีการตรวจสอบสิทธิ์ JWT ด้วย Angular 6 SPA
เผยแพร่แล้ว: 2022-03-11วันนี้เราจะมาดูกันว่าการรวมการพิสูจน์ตัวตนโทเค็นเว็บ (JWT) ของ JSON เข้ากับแอปพลิเคชันหน้าเดียว (SPA) ของ Angular 6 (หรือใหม่กว่า) นั้นง่ายเพียงใด เริ่มจากพื้นหลังกันสักหน่อย
JSON Web Token คืออะไรและทำไมจึงต้องใช้
คำตอบที่ง่ายและกระชับที่สุดคือสะดวก กะทัดรัด และปลอดภัย ดูรายละเอียดการอ้างสิทธิ์เหล่านั้น:
- สะดวก : การใช้ JWT สำหรับการตรวจสอบสิทธิ์ที่ส่วนหลังเมื่อเข้าสู่ระบบแล้ว จะต้องตั้งค่าส่วนหัว HTTP หนึ่งรายการ ซึ่งเป็นงานที่สามารถทำให้เป็นอัตโนมัติได้อย่างง่ายดายผ่านฟังก์ชันหรือคลาสย่อย ดังที่เราเห็นในภายหลัง
- กะทัดรัด : โทเค็นเป็นเพียงสตริงที่เข้ารหัสฐาน 64 ซึ่งประกอบด้วยฟิลด์ส่วนหัวสองสามฟิลด์ และเพย์โหลด หากจำเป็น โดยทั่วไปแล้ว JWT ทั้งหมดจะน้อยกว่า 200 ไบต์ แม้ว่าจะลงนามแล้วก็ตาม
- ปลอดภัย : แม้ว่าจะไม่จำเป็น แต่ฟีเจอร์ความปลอดภัยที่ยอดเยี่ยมของ JWT ก็คือสามารถเซ็นชื่อโทเค็นโดยใช้การเข้ารหัสคู่คีย์สาธารณะ/ส่วนตัว RSA หรือการเข้ารหัส HMAC โดยใช้ความลับที่ใช้ร่วมกัน สิ่งนี้ทำให้มั่นใจถึงที่มาและความถูกต้องของโทเค็น
ทั้งหมดนี้ทำให้คุณต้องมีวิธีที่ปลอดภัยและมีประสิทธิภาพในการตรวจสอบผู้ใช้ จากนั้นตรวจสอบการเรียกไปยังจุดปลาย API ของคุณโดยไม่ต้องแยกวิเคราะห์โครงสร้างข้อมูลใดๆ หรือใช้การเข้ารหัสของคุณเอง
ทฤษฎีการสมัคร
ด้วยพื้นหลังเล็กน้อย ตอนนี้เราสามารถเจาะลึกถึงวิธีการทำงานนี้ในแอปพลิเคชันจริง สำหรับตัวอย่างนี้ ฉันจะถือว่าเรามีเซิร์ฟเวอร์ Node.js ที่โฮสต์ API ของเรา และเรากำลังพัฒนารายการสิ่งที่ต้องทำของ SPA โดยใช้ Angular 6 มาทำงานกับโครงสร้าง API นี้ด้วย:
-
/auth
→POST
(โพสต์ชื่อผู้ใช้และรหัสผ่านเพื่อตรวจสอบสิทธิ์และรับ JWT กลับ) -
/todos
→GET
(ส่งคืนรายการสิ่งที่ต้องทำสำหรับผู้ใช้) -
/todos/{id}
→GET
(ส่งคืนรายการสิ่งที่ต้องทำเฉพาะ) -
/users
→GET
(ส่งคืนรายชื่อผู้ใช้)
เราจะดำเนินการสร้างแอปพลิเคชันง่ายๆ นี้ในไม่ช้า แต่สำหรับตอนนี้ เรามาเน้นที่ปฏิสัมพันธ์ในทางทฤษฎีกัน เรามีหน้าเข้าสู่ระบบแบบง่ายๆ ซึ่งผู้ใช้สามารถป้อนชื่อผู้ใช้และรหัสผ่านได้ เมื่อส่งแบบฟอร์ม จะส่งข้อมูลนั้นไปยังปลายทาง /auth
จากนั้นเซิร์ฟเวอร์ Node จะตรวจสอบสิทธิ์ผู้ใช้ในรูปแบบใดก็ได้ที่เหมาะสม (การค้นหาฐานข้อมูล การสอบถามบริการเว็บอื่น ฯลฯ) แต่ปลายทางจำเป็นต้องส่งคืน JWT ในท้ายที่สุด
JWT สำหรับตัวอย่างนี้จะประกอบด้วยการอ้างสิทธิ์ที่ สงวนไว้ สองสามข้อ และ การอ้างสิทธิ์ส่วนตัว บางส่วน การอ้างสิทธิ์ที่สงวนไว้เป็นเพียงคู่คีย์-ค่าที่แนะนำโดย JWT ซึ่งมักใช้สำหรับการตรวจสอบสิทธิ์ ในขณะที่การอ้างสิทธิ์ส่วนตัวเป็นคู่คีย์-ค่าที่ใช้ได้กับแอปของเราเท่านั้น:
การอ้างสิทธิ์ที่สงวนไว้
-
iss
: ผู้ออกโทเค็นนี้ โดยทั่วไปแล้วจะเป็น FQDN ของเซิร์ฟเวอร์ แต่สามารถเป็นอะไรก็ได้ตราบใดที่แอปพลิเคชันไคลเอนต์รู้ว่าคาดหวัง -
exp
: วันหมดอายุและเวลาของโทเค็นนี้ นี่คือหน่วยวินาทีตั้งแต่เที่ยงคืน 01 มกราคม 1970 GMT (เวลา Unix) -
nbf
: ไม่ถูกต้องก่อนประทับเวลา ไม่ได้ใช้บ่อย แต่ให้ขอบเขตล่างสำหรับหน้าต่างความถูกต้อง รูปแบบเดียวกับexp
เรียกร้องส่วนตัว
-
uid
: ID ผู้ใช้ของผู้ใช้ที่เข้าสู่ระบบ -
role
: บทบาทที่กำหนดให้กับผู้ใช้ที่เข้าสู่ระบบ
ข้อมูลของเราจะถูกเข้ารหัสฐาน 64 และลงนามโดยใช้ HMAC ด้วยรหัสที่ใช้ร่วมกัน todo-app-super-shared-secret
ด้านล่างนี้เป็นตัวอย่างของลักษณะของ JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b2RvYXBpIiwibmJmIjoxNDk4MTE3NjQyLCJleHAiOjE0OTgxMjEyNDIsInVpZCI6MSwicm9sZSI6ImFkbWluIn0.ZDz_1vcIlnZz64nSM28yA1s-4c_iw3Z2ZtP-SgcYRPQ
สตริง นี้ คือทั้งหมดที่เราต้องใช้เพื่อให้แน่ใจว่ามีการเข้าสู่ระบบที่ถูกต้อง เพื่อให้ทราบว่าผู้ใช้รายใดเชื่อมต่ออยู่ และเพื่อให้ทราบว่าผู้ใช้มีบทบาทใด
ไลบรารีและแอปพลิเคชันส่วนใหญ่ดำเนินการจัดเก็บ JWT นี้ใน localStorage
หรือ sessionStorage
เพื่อให้ดึงข้อมูลได้ง่าย แต่นี่เป็นเพียงแนวทางปฏิบัติทั่วไป สิ่งที่คุณทำกับโทเค็นนั้นขึ้นอยู่กับคุณ ตราบใดที่คุณสามารถให้โทเค็นนั้นสำหรับการเรียก API ในอนาคต
ตอนนี้ เมื่อใดก็ตามที่ SPA ต้องการโทรไปยังจุดปลาย API ที่ได้รับการป้องกันใดๆ ก็เพียงแค่ส่งโทเค็นไปที่ส่วนหัว HTTP Authorization
Authorization: Bearer {JWT Token}
หมายเหตุ : นี่เป็นวิธีปฏิบัติทั่วไปอีกครั้งหนึ่ง JWT ไม่ได้กำหนดวิธีการเฉพาะสำหรับส่งตัวเองไปยังเซิร์ฟเวอร์ คุณยังสามารถต่อท้าย URL หรือส่งในคุกกี้
เมื่อเซิร์ฟเวอร์ได้รับ JWT ก็สามารถถอดรหัสได้ ตรวจสอบความสอดคล้องโดยใช้ความลับที่ใช้ร่วมกันของ HMAC และตรวจสอบวันหมดอายุโดยใช้ฟิลด์ exp
และ nbf
นอกจากนี้ยังสามารถใช้ฟิลด์ iss
เพื่อให้แน่ใจว่าเป็นฝ่ายที่ออกต้นฉบับของ JWT นี้
เมื่อเซิร์ฟเวอร์พอใจกับความถูกต้องของโทเค็นแล้ว ก็จะสามารถใช้ข้อมูลที่จัดเก็บไว้ใน JWT ได้ ตัวอย่างเช่น uid
ที่เรารวมไว้จะให้ ID ของผู้ใช้ที่ส่งคำขอ สำหรับตัวอย่างเฉพาะนี้ เรายังรวมฟิลด์ role
ซึ่งช่วยให้เราตัดสินใจได้ว่าผู้ใช้ควรสามารถเข้าถึงจุดปลายเฉพาะหรือไม่ (ไม่ว่าคุณจะเชื่อถือข้อมูลนี้หรือต้องการค้นหาฐานข้อมูลก็ตามขึ้นอยู่กับระดับความปลอดภัยที่จำเป็น)
function getTodos(jwtString) { var token = JWTDecode(jwtstring); if( Date.now() < token.nbf*1000) { throw new Error('Token not yet valid'); } if( Date.now() > token.exp*1000) { throw new Error('Token has expired'); } if( token.iss != 'todoapi') { throw new Error('Token not issued here'); } var userID = token.uid; var todos = loadUserTodosFromDB(userID); return JSON.stringify(todos); }
มาสร้างแอป Todo ง่ายๆ กันเถอะ
ในการปฏิบัติตาม คุณจะต้องติดตั้ง Node.js (6.x หรือใหม่กว่า), npm (3.x หรือใหม่กว่า) และ angular-cli เวอร์ชันล่าสุด หากคุณต้องการติดตั้ง Node.js ซึ่งรวมถึง npm โปรดทำตามคำแนะนำที่นี่ หลังจากนั้นสามารถติดตั้ง angular-cli
ได้โดยใช้ npm
(หรือ yarn
หากคุณได้ติดตั้งไว้):
# installation using npm npm install -g @angular/cli # installation using yarn yarn global add @angular/cli
ฉันจะไม่ลงรายละเอียดเกี่ยวกับสำเร็จรูปของ Angular 6 ที่เราจะใช้ที่นี่ แต่สำหรับขั้นตอนต่อไป ฉันได้สร้างที่เก็บ Github เพื่อเก็บแอปพลิเคชันสิ่งที่ต้องทำขนาดเล็กเพื่อแสดงความเรียบง่ายของการเพิ่มการพิสูจน์ตัวตน JWT ให้กับแอปของคุณ เพียงโคลนโดยใช้สิ่งต่อไปนี้:
git clone https://github.com/sschocke/angular-jwt-todo.git cd angular-jwt-todo git checkout pre-jwt
คำสั่ง git checkout pre-jwt
จะเปลี่ยนไปใช้การตั้งชื่อโดยที่ JWT ยังไม่ได้ใช้งาน
ควรจะมีสองโฟลเดอร์ภายในที่เรียกว่า server
และ client
เซิร์ฟเวอร์เป็นเซิร์ฟเวอร์ Node API ที่จะโฮสต์ API พื้นฐานของเรา ลูกค้าคือแอป Angular 6 ของเรา
เซิร์ฟเวอร์ Node API
ในการเริ่มต้น ให้ติดตั้งการพึ่งพาและเริ่มต้นเซิร์ฟเวอร์ API
cd server # installation using npm npm install # or installation using yarn yarn node app.js
คุณควรติดตามลิงก์เหล่านี้และรับข้อมูลแทน JSON ในตอนนี้ จนกว่าเราจะมีการตรวจสอบสิทธิ์ เราได้ฮาร์ดโค้ดปลายทาง /todos
เพื่อส่งคืนงานสำหรับ userID=1
:
- http://localhost:4000: หน้าทดสอบเพื่อดูว่าเซิร์ฟเวอร์โหนดกำลังทำงานอยู่หรือไม่
- http://localhost:4000/api/users: ส่งคืนรายชื่อผู้ใช้ในระบบ
- http://localhost:4000/api/todos: ส่งคืนรายการงานสำหรับ
userID=1
แอพเชิงมุม
ในการเริ่มต้นใช้งานแอปไคลเอ็นต์ เราต้องติดตั้งการพึ่งพาและเริ่มต้นเซิร์ฟเวอร์ dev
cd client # using npm npm install npm start # using yarn yarn yarn start
หมายเหตุ : ขึ้นอยู่กับความเร็วสายของคุณ อาจใช้เวลาสักครู่ในการดาวน์โหลดการพึ่งพาทั้งหมด
หากทุกอย่างเป็นไปด้วยดี คุณควรเห็นสิ่งนี้เมื่อนำทางไปยัง http://localhost:4200:
การเพิ่มการตรวจสอบสิทธิ์ผ่าน JWT
เพื่อเพิ่มการสนับสนุนสำหรับการตรวจสอบ JWT เราจะใช้ประโยชน์จากไลบรารีมาตรฐานบางตัวที่พร้อมใช้งานซึ่งทำให้ง่ายขึ้น แน่นอน คุณสามารถละทิ้งสิ่งอำนวยความสะดวกเหล่านี้และดำเนินการทุกอย่างด้วยตนเอง แต่นั่นอยู่นอกเหนือขอบเขตของเราที่นี่
ขั้นแรก มาติดตั้งไลบรารีที่ฝั่งไคลเอ็นต์กันก่อน ได้รับการพัฒนาและดูแลโดย Auth0 ซึ่งเป็นไลบรารีที่ให้คุณเพิ่มการรับรองความถูกต้องบนคลาวด์ไปยังเว็บไซต์ได้ การใช้ห้องสมุดเองไม่ได้กำหนดให้คุณต้องใช้บริการของห้องสมุด
cd client # installation using npm npm install @auth0/angular-jwt # installation using yarn yarn add @auth0/angular-jwt
เราจะไปที่โค้ดในอีกสักครู่ แต่ในขณะที่เรากำลังดำเนินการอยู่ เรามาตั้งค่าฝั่งเซิร์ฟเวอร์กันด้วย เราจะใช้ body-parser
, jsonwebtoken
และไลบรารี express-jwt
เพื่อให้ Node เข้าใจเนื้อหา JSON POST และ JWT
cd server # installation using npm npm install body-parser jsonwebtoken express-jwt # installation using yarn yarn add body-parser jsonwebtoken express-jwt
ปลายทาง API สำหรับการตรวจสอบสิทธิ์
อันดับแรก เราต้องการวิธีตรวจสอบสิทธิ์ผู้ใช้ก่อนที่จะมอบโทเค็นให้พวกเขา สำหรับการสาธิตอย่างง่ายของเรา เราจะตั้งค่าจุดสิ้นสุดการตรวจสอบสิทธิ์แบบตายตัวด้วยชื่อผู้ใช้และรหัสผ่านแบบฮาร์ดโค้ด สามารถทำได้ง่ายหรือซับซ้อนตามที่แอปพลิเคชันของคุณต้องการ สิ่งสำคัญคือการส่ง JWT กลับ
ใน server/app.js
ให้เพิ่มรายการใต้บรรทัดที่ require
อื่น ๆ ดังนี้:
const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt');
ตลอดจนสิ่งต่อไปนี้:
app.use(bodyParser.json()); app.post('/api/auth', function(req, res) { const body = req.body; const user = USERS.find(user => user.username == body.username); if(!user || body.password != 'todo') return res.sendStatus(401); var token = jwt.sign({userID: user.id}, 'todo-app-super-shared-secret', {expiresIn: '2h'}); res.send({token}); });
ส่วนใหญ่เป็นโค้ด JavaScript พื้นฐาน เราได้รับเนื้อหา JSON ที่ส่งผ่านไปยังปลายทาง /auth
ค้นหาผู้ใช้ที่ตรงกับชื่อผู้ใช้นั้น ตรวจสอบว่าเรามีผู้ใช้และรหัสผ่านตรงกัน และส่งคืนข้อผิดพลาด 401 Unauthorized
HTTP หากไม่เป็นเช่นนั้น
ส่วนที่สำคัญคือการสร้างโทเค็น และเราจะแยกส่วนนั้นออกเป็นสามพารามิเตอร์ ไวยากรณ์สำหรับ sign
มีดังนี้: jwt.sign(payload, secretOrPrivateKey, [options, callback])
, โดยที่:
-
payload
เป็นอ็อบเจ็กต์ตามตัวอักษรของคู่คีย์-ค่าที่คุณต้องการเข้ารหัสภายในโทเค็นของคุณ ข้อมูลนี้สามารถถอดรหัสจากโทเค็นโดยใครก็ตามที่มีคีย์ถอดรหัส ในตัวอย่างของเรา เราเข้ารหัสuser.id
เพื่อที่ว่าเมื่อเราได้รับโทเค็นอีกครั้งที่ส่วนหลังสำหรับการตรวจสอบสิทธิ์ เราจะรู้ว่าเรากำลังติดต่อกับผู้ใช้รายใด -
secretOrPrivateKey
เป็นทั้งรหัสลับที่ใช้ร่วมกันสำหรับการเข้ารหัส HMAC—นี่คือสิ่งที่เราใช้ในแอปของเรา เพื่อความเรียบง่าย—หรือคีย์ส่วนตัวเข้ารหัส RSA/ECDSA -
options
แสดงถึงตัวเลือกที่หลากหลายที่สามารถส่งผ่านไปยังตัวเข้ารหัสในรูปแบบของคู่คีย์-ค่าได้ โดยปกติ อย่างน้อยเราระบุexpiresIn
(กลายเป็นการอ้างสิทธิ์ที่สงวนไว้exp
) และissuer
(iss
การอ้างสิทธิ์ที่สงวนไว้) เพื่อให้โทเค็นไม่สามารถใช้งานได้ตลอดไป และเซิร์ฟเวอร์สามารถตรวจสอบว่าได้ออกโทเค็นแล้วจริงๆ หรือไม่ -
callback
เป็นฟังก์ชันที่จะเรียกหลังจากการเข้ารหัสเสร็จสิ้นแล้ว หากต้องการจัดการกับการเข้ารหัสโทเค็นแบบอะซิงโครนัส
(คุณยังสามารถอ่านรายละเอียดเพิ่มเติมเกี่ยวกับ options
และวิธีใช้การเข้ารหัสคีย์สาธารณะแทนคีย์ลับที่ใช้ร่วมกันได้)
การรวม JWT เชิงมุม 6
เพื่อให้ Angular 6 ทำงานกับ JWT ของเรานั้นค่อนข้างง่ายโดยใช้ angular-jwt
เพียงเพิ่มสิ่งต่อไปนี้ใน client/src/app/app.modules.ts
:
import { JwtModule } from '@auth0/angular-jwt'; // ... export function tokenGetter() { return localStorage.getItem('access_token'); } @NgModule({ // ... imports: [ BrowserModule, AppRoutingModule, HttpClientModule, FormsModule, // Add this import here JwtModule.forRoot({ config: { tokenGetter: tokenGetter, whitelistedDomains: ['localhost:4000'], blacklistedRoutes: ['localhost:4000/api/auth'] } }) ], // ... }
นั่นคือทั้งหมดที่จำเป็น แน่นอน เรามีโค้ดเพิ่มเติมที่จะเพิ่มเพื่อทำการตรวจสอบเบื้องต้น แต่ไลบรารี angular-jwt
จะดูแลการส่งโทเค็นไปพร้อมกับคำขอ HTTP ทุกครั้ง

-
tokenGetter()
ทำตามที่กล่าวไว้ทุกประการ แต่วิธีการนำไปใช้นั้นขึ้นอยู่กับคุณ เราได้เลือกที่จะส่งคืนโทเค็นที่เราบันทึกไว้ในlocalStorage
คุณมีอิสระที่จะจัดเตรียมวิธีการอื่นใดที่คุณต้องการ ตราบใดที่มันส่งคืนสตริงที่เข้ารหัส โทเค็นเว็บ JSON - มีตัวเลือก
whiteListedDomains
เพื่อให้คุณสามารถจำกัดโดเมนที่ส่ง JWT ไป ดังนั้น API สาธารณะจะไม่ได้รับ JWT ของคุณด้วย - ตัวเลือก
blackListedRoutes
ช่วยให้คุณระบุเส้นทางเฉพาะที่ไม่ควรรับ JWT แม้ว่าจะอยู่ในโดเมนที่อนุญาตพิเศษ ตัวอย่างเช่น ปลายทางการตรวจสอบสิทธิ์ไม่จำเป็นต้องได้รับเพราะไม่มีจุดใด: โดยทั่วไปแล้วโทเค็นจะเป็นโมฆะเมื่อมีการเรียกอย่างไรก็ตาม
ทำให้ทุกอย่างทำงานร่วมกันได้
ณ จุดนี้ เรามีวิธีสร้าง JWT สำหรับผู้ใช้ที่กำหนดโดยใช้ปลายทาง /auth
บน API ของเรา และเราได้ทำระบบประปาบน Angular เพื่อส่ง JWT กับทุกคำขอ HTTP ดีมาก แต่คุณอาจชี้ให้เห็นว่าไม่มีอะไรเปลี่ยนแปลงสำหรับผู้ใช้อย่างแน่นอน และคุณจะถูกต้อง เรายังคงสามารถนำทางไปยังทุกหน้าในแอปของเรา และเราสามารถเรียกตำแหน่งข้อมูล API ใดๆ โดยไม่ต้องส่ง JWT ด้วยซ้ำ ไม่ดี!
เราจำเป็นต้องอัปเดตแอปไคลเอ็นต์เพื่อให้กังวลเกี่ยวกับผู้ที่เข้าสู่ระบบ และอัปเดต API ของเราให้ต้องใช้ JWT มาเริ่มกันเลย.
เราต้องการองค์ประกอบ Angular ใหม่สำหรับการเข้าสู่ระบบ เพื่อความกระชับ ฉันจะทำให้มันง่ายที่สุดเท่าที่จะทำได้ เรายังต้องการบริการที่จะจัดการกับข้อกำหนดการรับรองความถูกต้องทั้งหมดของเรา และ Angular Guard เพื่อปกป้องเส้นทางที่ไม่ควรเข้าถึงก่อนเข้าสู่ระบบ เราจะทำสิ่งต่อไปนี้ในบริบทของแอปพลิเคชันไคลเอนต์
cd client ng g component login --spec=false --inline-style ng g service auth --flat --spec=false ng g guard auth --flat --spec=false
สิ่งนี้ควรสร้างไฟล์ใหม่ สี่ ไฟล์ในโฟลเดอร์ client
:
src/app/login/login.component.html src/app/login/login.component.ts src/app/auth.service.ts src/app/auth.guard.ts
ต่อไป เราต้องให้บริการตรวจสอบและป้องกันแอปของเรา อัปเดต client/src/app/app.modules.ts
:
import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; // ... providers: [ TodoService, UserService, AuthService, AuthGuard ],
จากนั้นอัปเดตการกำหนดเส้นทางใน client/src/app/app-routing.modules.ts
เพื่อใช้งานยามรับรองความถูกต้องและจัดหาเส้นทางสำหรับองค์ประกอบการเข้าสู่ระบบ
// ... import { LoginComponent } from './login/login.component'; import { AuthGuard } from './auth.guard'; const routes: Routes = [ { path: 'todos', component: TodoListComponent, canActivate: [AuthGuard] }, { path: 'users', component: UserListComponent, canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent}, // ...
สุดท้าย อัปเดต client/src/app/auth.guard.ts
ด้วยเนื้อหาต่อไปนี้:
import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router) { } canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (localStorage.getItem('access_token')) { return true; } this.router.navigate(['login']); return false; } }
สำหรับแอปพลิเคชันสาธิตของเรา เราเพียงแค่ตรวจสอบการมีอยู่ของ JWT ในที่จัดเก็บในตัวเครื่อง ในแอปพลิเคชันในโลกแห่งความเป็นจริง คุณจะต้องถอดรหัสโทเค็นและตรวจสอบความถูกต้อง การหมดอายุ ฯลฯ ตัวอย่างเช่น คุณสามารถใช้ JwtHelperService สำหรับสิ่งนี้
ณ จุดนี้ แอป Angular ของเราจะเปลี่ยนเส้นทางคุณไปยังหน้าเข้าสู่ระบบเสมอ เนื่องจากเราไม่มีทางเข้าสู่ระบบ มาแก้ไขกันโดยเริ่มจากบริการตรวจสอบสิทธิ์ใน client/src/app/auth.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class AuthService { constructor(private http: HttpClient) { } login(username: string, password: string): Observable<boolean> { return this.http.post<{token: string}>('/api/auth', {username: username, password: password}) .pipe( map(result => { localStorage.setItem('access_token', result.token); return true; }) ); } logout() { localStorage.removeItem('access_token'); } public get loggedIn(): boolean { return (localStorage.getItem('access_token') !== null); } }
บริการรับรองความถูกต้องของเรามีเพียงสองฟังก์ชั่นเท่านั้น login
และ logout
:
-
login
POST
เป็นusername
และpassword
ที่ให้มาที่ส่วนหลังของเรา และตั้งค่าaccess_token
ในlocalStorage
หากได้รับกลับมา เพื่อความเรียบง่าย ไม่มีข้อผิดพลาดในการจัดการที่นี่ -
logout
เพียงล้างaccess_token
จากlocalStorage
โดยต้องได้รับโทเค็นใหม่ก่อนที่จะสามารถเข้าถึงอะไรได้อีก -
loggedIn
ระบบเป็นคุณสมบัติบูลีนที่เราสามารถใช้อย่างรวดเร็วเพื่อตรวจสอบว่าผู้ใช้เข้าสู่ระบบหรือไม่
และสุดท้าย องค์ประกอบการเข้าสู่ระบบ สิ่งเหล่านี้ไม่มีความสัมพันธ์กับการทำงานจริงกับ JWT ดังนั้นอย่าลังเลที่จะคัดลอกและวางใน client/src/app/login/login.components.html
:
<h4 *ngIf="error">{{error}}</h4> <form (ngSubmit)="submit()"> <div class="form-group col-3"> <label for="username">Username</label> <input type="text" name="username" class="form-control" [(ngModel)]="username" /> </div> <div class="form-group col-3"> <label for="password">Password</label> <input type="password" name="password" class="form-control" [(ngModel)]="password" /> </div> <div class="form-group col-3"> <button class="btn btn-primary" type="submit">Login</button> </div> </form>
และ client/src/app/login/login.components.ts
จะต้อง:
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { Router } from '@angular/router'; import { first } from 'rxjs/operators'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { public username: string; public password: string; public error: string; constructor(private auth: AuthService, private router: Router) { } public submit() { this.auth.login(this.username, this.password) .pipe(first()) .subscribe( result => this.router.navigate(['todos']), err => this.error = 'Could not authenticate' ); } }
Voila ตัวอย่างการเข้าสู่ระบบ Angular 6 ของเรา:
ในขั้นตอนนี้ เราควรจะสามารถเข้าสู่ระบบได้ (โดยใช้ jemma
, paul
หรือ sebastian
ด้วยรหัสผ่าน todo
) และดูหน้าจอทั้งหมดอีกครั้ง แต่แอปพลิเคชันของเราแสดงส่วนหัวการนำทางเดียวกันและไม่มีทางออกจากระบบโดยไม่คำนึงถึงสถานะปัจจุบัน มาแก้ไขกันก่อนที่เราจะไปแก้ไข API ของเรา
ใน client/src/app/app.component.ts
ให้แทนที่ไฟล์ทั้งหมดดังต่อไปนี้:
import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private auth: AuthService, private router: Router) { } logout() { this.auth.logout(); this.router.navigate(['login']); } }
และสำหรับ client/src/app/app.component.html
ให้แทนที่ส่วน <nav>
ด้วยสิ่งต่อไปนี้:
<nav class="nav nav-pills"> <a class="nav-link" routerLink="todos" routerLinkActive="active" *ngIf="auth.loggedIn">Todo List</a> <a class="nav-link" routerLink="users" routerLinkActive="active" *ngIf="auth.loggedIn">Users</a> <a class="nav-link" routerLink="login" routerLinkActive="active" *ngIf="!auth.loggedIn">Login</a> <a class="nav-link" (click)="logout()" href="#" *ngIf="auth.loggedIn">Logout</a> </nav>
เราได้ทำให้การนำทางของเราทราบบริบทว่าควรแสดงเฉพาะบางรายการขึ้นอยู่กับว่าผู้ใช้เข้าสู่ระบบหรือไม่ auth.loggedIn
สามารถใช้ได้ทุกที่ที่คุณสามารถนำเข้าบริการรับรองความถูกต้องได้
การรักษาความปลอดภัย API
คุณอาจกำลังคิดว่า นี่มันเยี่ยมมาก… ทุกอย่างดูเหมือนจะทำงานได้อย่างยอดเยี่ยม แต่ลองเข้าสู่ระบบด้วยชื่อผู้ใช้ที่แตกต่างกันทั้งสามแล้วคุณจะสังเกตเห็นบางสิ่ง: พวกเขาทั้งหมดส่งคืนรายการสิ่งที่ต้องทำเดียวกัน หากเราดูที่เซิร์ฟเวอร์ API ของเรา เราจะเห็นได้ว่าผู้ใช้แต่ละคนมีรายการของตัวเองจริงๆ แล้วเป็นอย่างไรบ้าง
จำไว้ว่าเมื่อเราเริ่มต้น เราได้เขียนโค้ดปลายทาง /todos
API เพื่อส่งคืนรายการสิ่งที่ต้องทำสำหรับ userID=1
เสมอ เนื่องจากเราไม่มีทางรู้ว่าใครเป็นผู้ใช้ที่เข้าสู่ระบบอยู่ในขณะนี้
ตอนนี้เราทำได้แล้ว มาดูกันว่ามันง่ายเพียงใดในการรักษาความปลอดภัยปลายทางของเราและใช้ข้อมูลที่เข้ารหัสใน JWT เพื่อระบุข้อมูลประจำตัวผู้ใช้ที่จำเป็น เริ่มแรก เพิ่มบรรทัดนี้ลงในไฟล์ server/app.js
ของคุณใต้การ app.use()
ครั้งสุดท้าย:
app.use(expressJwt({secret: 'todo-app-super-shared-secret'}).unless({path: ['/api/auth']}));
เราใช้มิดเดิลแวร์ express-jwt
เพื่อบอกว่าความลับที่แชร์คืออะไร และระบุอาร์เรย์ของพาธที่ไม่ต้องการให้ JWT ทำ และนั่นแหล่ะ ไม่จำเป็นต้องแตะจุดสิ้นสุดทุกจุด สร้างคำสั่ง if
ทั้งหมด หรืออะไรก็ตาม
ภายในมิดเดิลแวร์มีการตั้งสมมติฐานบางประการ ตัวอย่างเช่น จะถือว่าส่วนหัว HTTP Authorization
เป็นไปตามรูปแบบ JWT ทั่วไปของ Bearer {token}
(ไลบรารี่มีตัวเลือกมากมายสำหรับการปรับแต่งวิธีการทำงาน หากไม่เป็นเช่นนั้น ดูรายละเอียดเพิ่มเติมเกี่ยวกับการใช้งาน express-jwt)
วัตถุประสงค์ที่สองของเราคือการใช้ข้อมูลที่เข้ารหัส JWT เพื่อค้นหาว่าใครเป็นผู้โทร อีกครั้งหนึ่ง express-jwt
มาช่วย ในส่วนของการอ่านโทเค็นและการตรวจสอบ โทเค็นจะตั้งค่าเพย์โหลดที่เข้ารหัสที่เราส่งไปในกระบวนการเซ็นชื่อเป็นตัวแปร req.user
ใน Express จากนั้นเราสามารถใช้เพื่อเข้าถึงตัวแปรที่เราเก็บไว้ได้ทันที ในกรณีของเรา เราตั้งค่า userID
ให้เท่ากับ ID ผู้ใช้ที่ตรวจสอบสิทธิ์ และด้วยเหตุนี้ เราจึงสามารถใช้เป็น req.user.userID
ได้โดยตรง
อัปเดต server/app.js
อีกครั้ง และเปลี่ยนปลายทาง /todos
ให้อ่านดังนี้:
res.send(getTodos(req.user.userID));
และนั่นแหล่ะ ตอนนี้ API ของเราปลอดภัยจากการเข้าถึงโดยไม่ได้รับอนุญาต และเราสามารถระบุได้อย่างปลอดภัยว่าใครคือผู้ใช้ที่ตรวจสอบสิทธิ์ของเราในจุดสิ้นสุดใดๆ แอปพลิเคชันไคลเอนต์ของเรายังมีกระบวนการตรวจสอบสิทธิ์อย่างง่าย และบริการ HTTP ใดๆ ที่เราเขียนที่เรียกใช้ปลายทาง API ของเราจะมีการแนบโทเค็นการตรวจสอบสิทธิ์โดยอัตโนมัติ
หากคุณโคลนที่เก็บ Github และเพียงต้องการดูผลลัพธ์สุดท้ายในการดำเนินการ คุณสามารถตรวจสอบโค้ดในแบบฟอร์มสุดท้ายได้โดยใช้:
git checkout with-jwt
ฉันหวังว่าคุณจะพบว่าคำแนะนำนี้มีประโยชน์สำหรับการเพิ่มการพิสูจน์ตัวตน JWT ให้กับแอป Angular ของคุณเอง ขอบคุณที่อ่าน!