在現代 Web 開發中,保障使用者身分的安全性是一個至關重要的課題。
本篇將深入討論使用 JWT 實作身分驗證和實際安全技巧,其中包括防範 SQL 注入和更詳細的 XSS 防護。
JSON Web Token (JWT) 是一種常見的身分驗證機制,它提供了安全而有效的方式來管理使用者身分。以下是在 Express 中實作 JWT 身分驗證的詳細步驟:
首先我們使用指令安裝 jsonwebtoken
:
- npm install jsonwebtoken
在身分驗證的過程中,不僅要能夠成功創建 JWT,還需要能夠確保 JWT 的內容是有效且沒有被篡改的。以下是創建和驗證 JWT 的詳細操作:
在/login
路由中,我們創建一個包含使用者資訊的 JWT:
- const jwt = require("jsonwebtoken");
- app.post("/login", (req, res) => {
- // 模擬使用者身分驗證
- const user = { id: 1, name: "Pardn Chiu"};
- // 創建 JWT,並設置過期時間
- jwt.sign({ user }, "[SecretKey]", { expiresIn: "1h" }, (err, token) => {
- if (err) {
- res.status(500).json({ err: err });
- }
- else {
- res.json({ token });
- };
- });
- });
在這個例子中,jwt.sign
函數接收三個參數:
{ user }
: 儲存在 JWT 中的使用者資訊。[SecretKey]
: 簽署 JWT 的密鑰,請妥善保管密鑰。{ expiresIn: "1h" }
: Token 過期時間,範例中設定為一小時。在路由/needAuth
中,我們使用jwt.verify
方法來驗證 JWT:
- const jwt = require("jsonwebtoken");
- app.get("/needAuth", getToken, (req, res) => {
- //驗證token內容
- jwt.verify(req.token, "[SecretKey]", (err, data) => {
- if (err) {
- res.sendStatus(403);
- }
- else {
- res.json({ message: "Success", data: data });
- };
- });
- });
- //取得token內容
- function getToken(req, res, next) {
- const authorization = req.headers["authorization"];
- if (typeof authorization !== "undefined") {
- const token = authorization.split(" ")[1];
- req.token = token;
- next();
- }
- else {
- res.sendStatus(403);
- };
- };
在這個例子中,jwt.verify
函數接收三個參數:
req.token
: 要驗證的 JWT。[SecretKey]
: 簽署 JWT 的密鑰,必須與創建 JWT 時使用的密鑰相同。data
將包含 JWT 中的資訊。這樣,我們就能確保只有在 JWT 是有效且未被篡改的情況下,才能讀取/needAuth
頁面。
SQL 注入是一種嚴重的安全威脅,攻擊者可能通過不當處理 SQL 查詢而獲取應用程式的資料庫訊息。以下是更詳細的 SQL 注入防護技巧:
使用參數來查詢是防範 SQL 注入的有效方法。這樣可以確保用戶輸入不會被直接插入 SQL 查詢:
- const mysql = require("mysql");
- const db = mysql.createConnection({
- host: "[HOST]",
- user: "[ACCOUNT]",
- password: "[PASSWORD]",
- database: "[DATABASE]"
- });
- app.get("/user/:id", async (req, res) => {
- const id = req.params.id;
- db.query(`
- SELECT * FROM users
- WHERE id = ?`, [
- id
- ], (err, r) => {
- // 處理結果
- });
- });
不要使用動態拼接 SQL 字符串,這可能會讓應用程式容易受到注入攻擊。而是使用預處理語句或 ORM (物件-關聯映射)來建構查詢。
如使用參數化查詢
範例中,?
是預處理語句的參數占位符,而不會將id
直接插入 SQL 查詢字符串。
ORM 是一種將應用程式中的物件與資料庫中的表格進行映射的技術。這樣的映射允許你使用物件導向的方式來操作資料庫,而不需要直接處理 SQL 查詢。
使用 ORM 可以大大減少動態拼接 SQL 字符串的風險,因為 ORM 通常會處理與資料庫的交互。以下是使用 Sequelize (一個 Node.js 中的 ORM) 的簡單例子:
- const Sequelize = require("sequelize");
- const sequelize = new Sequelize(
- "[DATABASE]",
- "[ACCOUNT]",
- "[password]", {
- host: "[HOST]",
- dialect: "mysql"
- }
- );
- // 定義 User 模型
- const User = sequelize.define("user", {
- id: {
- type: Sequelize.INTEGER,
- primaryKey: true,
- autoIncrement: true
- },
- name: Sequelize.STRING,
- });
- app.get("/user/:id", async (req, res) => {
- const id = req.params.id;
- try {
- const user = await User.findByPk(id);
- res.json(user);
- } catch (err) {
- res.status(500).json({ error: err });
- }
- });
在這個例子中,User 模型代表了資料庫中的users
表格,而不需要直接使用 SQL 查詢。
跨站腳本攻擊是另一種威脅,攻擊者可能通過在網頁上插入惡意腳本,竊取用戶信息。以下是更詳細的 XSS 防護技巧:
輸出轉義是一種機制,它將用戶輸入的特殊字元轉換為其對應的 HTML 實體,從而防止這些字元被解釋為 HTML 或 JavaScript 代碼。這樣可以防止潛在的 XSS 攻擊。
在使用模板引擎時,通常會自動處理輸出轉義。模板引擎會將用戶輸入的字元轉換為安全的 HTML 實體,確保其不會執行任何惡意的腳本。
雖然輸出轉義是一個有效的防禦手段,但還有其他進階的安全機制可以增加對抗 XSS 攻擊的強度。其中之一是 Content Security Policy (CSP)。
CSP 是一個 HTTP 標頭,它可以告訴瀏覽器僅執行特定來源的資源,從而有效地防止不信任的腳本的執行。在 Express 中,你可以添加 CSP 標頭:
- app.use((req, res, next) => {
- res.setHeader("Content-Security-Policy", "script-src \'self\'");
- next();
- });
上方範例中,script-src \'self\'
表示只允許執行來自相同來源的腳本。這有助於防止執行來自其他來源的惡意腳本。