วิธีรักษาความปลอดภัย GraphQL API: การใช้การรับรองความถูกต้องผู้ใช้ใน Express.js โดยใช้ JWT
GraphQL เป็นทางเลือกยอดนิยมแทนสถาปัตยกรรม RESTful API แบบดั้งเดิม โดยนำเสนอการสืบค้นข้อมูลและภาษาการจัดการที่ยืดหยุ่นและมีประสิทธิภาพสำหรับ API ด้วยการนำไปใช้ที่เพิ่มมากขึ้น การจัดลำดับความสำคัญด้านความปลอดภัยของ GraphQL API จึงมีความสำคัญมากขึ้น เพื่อปกป้องแอปพลิเคชันจากการเข้าถึงโดยไม่ได้รับอนุญาตและการละเมิดข้อมูลที่อาจเกิดขึ้น
กลยุทธ์หนึ่งที่ใช้ได้จริงในการปกป้อง GraphQL API เกี่ยวข้องกับการใช้ JSON Web Tokens (JWT) ซึ่งเสนอวิธีการที่มีประสิทธิภาพในการให้สิทธิ์การเข้าถึงทรัพยากรที่ถูกจำกัด และดำเนินการดำเนินการที่มีสิทธิพิเศษ ในขณะเดียวกันก็รักษาการสื่อสารที่ปลอดภัยระหว่างไคลเอนต์และ API
การรับรองความถูกต้องและการอนุญาตใน GraphQL API
ตรงกันข้ามกับ RESTful Application Programming Interfaces (API) การใช้งาน GraphQL API มักจะมีจุดสิ้นสุดเดียวที่ช่วยให้ลูกค้าสามารถขอข้อมูลในปริมาณและประเภทที่หลากหลายผ่านการสืบค้น ความสามารถในการปรับตัวนี้แสดงถึงทั้งข้อดีและแหล่งที่มาของความเสี่ยงด้านความปลอดภัย โดยเฉพาะอย่างยิ่งในส่วนที่เกี่ยวข้องกับกลไกการควบคุมการเข้าถึงที่ถูกบุกรุก
เพื่อจัดการภัยคุกคามที่กล่าวมาข้างต้นได้อย่างมีประสิทธิภาพ จำเป็นอย่างยิ่งที่จะต้องสร้างโปรโตคอลการตรวจสอบสิทธิ์และการอนุญาตที่ครอบคลุม ซึ่งรวมถึงการกำหนดสิทธิพิเศษในการเข้าถึงอย่างพิถีพิถัน ด้วยมาตรการดังกล่าว เรารับรองว่าเฉพาะบุคคลที่ได้รับอนุญาตเท่านั้นจึงจะสามารถเข้าถึงทรัพย์สินที่มีความละเอียดอ่อนได้ ซึ่งจะช่วยลดความเสี่ยงของช่องโหว่ด้านความปลอดภัยและเหตุการณ์การรั่วไหลของข้อมูล
[แทรกลิงก์ที่เก็บ GitHub ที่นี่]
ตั้งค่าเซิร์ฟเวอร์ Express.js Apollo
Apollo Server คือการใช้งานเซิร์ฟเวอร์ GraphQL ที่ใช้กันอย่างแพร่หลายสำหรับ GraphQL API คุณสามารถใช้เพื่อสร้างสคีมา GraphQL กำหนดรีโซลเวอร์ และจัดการแหล่งข้อมูลต่างๆ สำหรับ API ของคุณได้อย่างง่ายดาย
ในการสร้าง Express.js Apollo Server จำเป็นต้องสร้างและเปิดไดเร็กทอรีโปรเจ็กต์
mkdir graphql-API-jwt
cd graphql-API-jwt
หากต้องการเริ่มต้นโปรเจ็กต์ Node.js ใหม่โดยใช้ Node Package Manager (npm) ให้ดำเนินการคำสั่งต่อไปนี้:
npm init --yes
ตอนนี้ให้ติดตั้งแพ็คเกจเหล่านี้
npm install apollo-server graphql mongoose jsonwebtokens dotenv
อันที่จริง จำเป็นอย่างยิ่งที่จะต้องสร้างไฟล์ server.js
ในโฟลเดอร์หลักของโปรเจ็กต์ และกำหนดค่าเซิร์ฟเวอร์โดยใช้สคริปต์ที่ให้มาดังนี้:
const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();
const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req }),
});
const MONGO_URI = process.env.MONGO_URI;
mongoose
.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to DB");
return server.listen({ port: 5000 });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.log(err.message);
});
เซิร์ฟเวอร์ GraphQL ได้รับการกำหนดค่าโดยใช้การรวมกันของ typeDefs
และ solvers
ซึ่งกำหนดสคีมาและการดำเนินการที่ API สามารถจัดการได้ นอกจากนี้ การใช้ตัวเลือก บริบท
ช่วยให้สามารถกำหนดค่าออบเจ็กต์ req
เพื่อรวมบริบทเฉพาะที่เกี่ยวข้องกับรีโซลเวอร์แต่ละตัวได้ ซึ่งช่วยให้เซิร์ฟเวอร์สามารถเข้าถึงข้อมูลเฉพาะคำขอ รวมถึงค่าส่วนหัว เพื่ออำนวยความสะดวกในการเรียกข้อมูลที่มีประสิทธิภาพมากขึ้น
สร้างฐานข้อมูล MongoDB
สร้างฐานข้อมูลใหม่ภายในการติดตั้ง MongoDB ในเครื่องของคุณ หรือใช้โซลูชันบนคลาวด์ที่นำเสนอโดย MongoDB Atlas โดยการตั้งค่าคลัสเตอร์และรับข้อมูลการเชื่อมต่อที่จำเป็น เมื่อคุณได้รับสตริง URI การเชื่อมต่อที่เหมาะสมแล้ว คุณสามารถดำเนินการสร้างไฟล์ .env
ใหม่ และป้อนรายละเอียดการเชื่อมต่อในรูปแบบที่ระบุได้
MONGO_URI="<mongo_connection_uri>"
กำหนดโมเดลข้อมูล
แน่นอนว่า นี่คือตัวอย่างวิธีกำหนดโมเดลข้อมูลสำหรับผู้ใช้ในไฟล์ Mongoose schema ชื่อ models/user.js
:javascriptconst mongoose=need(‘mongoose’);//Define the user schemaconst userSchema=new mongoose.Schema ({ชื่อ: { ประเภท: สตริง, จำเป็น: จริง }, อีเมล: { ประเภท: สตริง, ไม่ซ้ำกัน: จริง, จำเป็น: จริง }, รหัสผ่าน: { ประเภท: สตริง, จำเป็น: จริง },});//รวบรวมสคีมา ลงใน modelconst User=mongoose.model(‘User’, userSchema);module.exports=User;ในตัวอย่างนี้ ก่อนอื่นเราจะนำเข้าไลบรารี Mongoose ที่ด้านบนของไฟล์ จากนั้นเราสร้างออบเจ็กต์ Schema ใหม่
const {model, Schema} = require('mongoose');
const userSchema = new Schema({
name: String,
password: String,
role: String
});
module.exports = model('user', userSchema);
กำหนดสคีมา GraphQL
สคีมาของ GraphQL API กำหนดการจัดเรียงข้อมูลที่สามารถเรียกคืนได้ นอกเหนือจากการระบุการดำเนินการที่เป็นไปได้ (การสอบถามและการเปลี่ยนแปลง) ซึ่งสามารถดำเนินการเพื่อสื่อสารกับข้อมูลผ่านอินเทอร์เฟซดังกล่าว
ในการสร้างสคีมาสำหรับโปรเจ็กต์ของคุณ คุณควรสร้างโฟลเดอร์ใหม่ภายในไดเร็กทอรีหลักของการดำเนินการของคุณก่อน ซึ่งจะเรียกว่า “graphql” โฟลเดอร์เฉพาะนี้จะทำหน้าที่เป็นวงล้อมซึ่งมีการจัดระเบียบส่วนประกอบทั้งหมดที่เกี่ยวข้องกับการดำเนินการ GraphQL ภายในขอบเขตของโฟลเดอร์ “graphql” จะต้องมีเอกสารสองฉบับที่แตกต่างกัน เอกสารหนึ่งชื่อ “typeDefs.js” และอีกเอกสารหนึ่งชื่อ “resolvers.js”
ในไฟล์ typeDefs.js
ให้รวมโค้ดต่อไปนี้:
const { gql } = require("apollo-server");
const typeDefs = gql`
type User {
id: ID!
name: String!
password: String!
role: String!
}
input UserInput {
name: String!
password: String!
role: String!
}
type TokenResult {
message: String
token: String
}
type Query {
users: [User]
}
type Mutation {
register(userInput: UserInput): User
login(name: String!, password: String!, role: String!): TokenResult
}
`;
module.exports = typeDefs;
สร้างตัวแก้ไขสำหรับ GraphQL API
ฟังก์ชันตัวแก้ไขมีบทบาทสำคัญในการกำหนดวิธีการรับข้อมูลเพื่อตอบสนองคำขอข้อมูลและการแก้ไขของลูกค้า รวมถึงรายละเอียดเพิ่มเติมใดๆ ที่ระบุภายในสคีมา เมื่อได้รับคำขอจากไคลเอนต์ เซิร์ฟเวอร์ GraphQL จะเปิดใช้งานรีโซลเวอร์ที่เกี่ยวข้องเพื่อรับข้อมูลจากหลายแหล่ง รวมถึงฐานข้อมูลและบริการภายนอก และส่งคืนตามนั้น
หากต้องการเปิดใช้งานการตรวจสอบสิทธิ์และการควบคุมการเข้าถึงโดยใช้ JSON Web Token (JWT) ในเซิร์ฟเวอร์ GraphQL ของเรา อันดับแรกเราต้องสร้างตัวแก้ไขสำหรับทั้งการเปลี่ยนแปลง"การลงทะเบียน"และ"เข้าสู่ระบบ"ตัวแก้ไขเหล่านี้มีหน้าที่จัดการขั้นตอนการสร้างบัญชีและการตรวจสอบ ต่อจากนั้น เราควรพัฒนาตัวแก้ไขการสืบค้นการดึงข้อมูลที่สามารถเข้าถึงได้โดยผู้ใช้ที่ลงทะเบียนและตรวจสอบแล้วเท่านั้นที่ได้รับอนุญาตให้ดำเนินการดังกล่าว
ตัวเข้ารหัสและตัวถอดรหัส RSA JWE จากแพ็คเกจ’node-jose’รวมถึงโมดูล’crypto’ที่จัดทำโดย Node.js สิ่งนี้จะช่วยให้เราสามารถดำเนินการเข้ารหัสที่เกี่ยวข้องกับการสร้างและการตรวจสอบ JWT ได้อย่างราบรื่น การทำเช่นนี้ทำให้เรามั่นใจได้ถึงการสื่อสารที่ปลอดภัยระหว่างแอปพลิเคชันของเราและผู้ใช้ ในขณะเดียวกันก็รักษาความเรียบง่ายและความสะดวกในการใช้งานสำหรับทั้งนักพัฒนาและผู้ใช้ปลายทาง
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
โปรดตรวจสอบให้แน่ใจว่าคุณได้รวมรหัสลับที่จำเป็นสำหรับการลงนาม JSON Web Tokens ไว้ในไฟล์.env ของคุณเพื่อใช้งานอย่างเหมาะสม
SECRET_KEY = '<my_Secret_Key>';
เพื่อสร้างโทเค็นการตรวจสอบความถูกต้องที่ถูกต้อง จำเป็นต้องใช้ฟังก์ชันที่ให้มาและระบุคุณสมบัติที่กำหนดเอง รวมถึงเวลาหมดอายุ ภายในโทเค็น JWT นอกจากนี้ อาจรวมคุณลักษณะเพิ่มเติม เช่น เวลาที่ออกให้ไว้ตามความต้องการเฉพาะของใบสมัคร
function generateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
);
return token;
}
เพื่อให้มั่นใจถึงความถูกต้องของ JSON Web Tokens (JWT) ที่ใช้ในการแลกเปลี่ยน HTTP ในอนาคต จำเป็นต้องรวมกระบวนการตรวจสอบความถูกต้องสำหรับโทเค็นเหล่านี้ สิ่งนี้จะเกิดขึ้นได้โดยใช้กลไกการรับรองความถูกต้องที่จะตรวจสอบและยืนยันความถูกต้องตามกฎหมายของ JWT แต่ละรายการที่นำเสนอในระหว่างการโต้ตอบดังกล่าว
function verifyToken(token) {
if (!token) {
throw new Error('Token not provided');
}
try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
throw new Error('Invalid token');
}
}
ฟังก์ชันที่กำหนดยอมรับโทเค็นเป็นอินพุต โดยใช้คีย์ลับที่กำหนดเพื่อตรวจสอบความถูกต้อง หากโทเค็นพิสูจน์ได้ว่าถูกต้องตามกฎหมาย ฟังก์ชันจะส่งคืนโทเค็นที่ถอดรหัสแล้ว อย่างไรก็ตาม ในกรณีที่มีความคลาดเคลื่อน จะทำให้เกิดข้อยกเว้นซึ่งบ่งชี้ว่ามีโทเค็นที่ไม่ถูกต้อง
กำหนดตัวแก้ไข API
เพื่ออธิบายกระบวนการแก้ไขปัญหาสำหรับ GraphQL API ของเรา เราต้องระบุงานเฉพาะหรือการดำเนินการที่จะดำเนินการ เช่น การจัดการการลงทะเบียนผู้ใช้และกระบวนการเข้าสู่ระบบ ขั้นแรก ให้สร้างอ็อบเจ็กต์ที่มีฟังก์ชันตัวแก้ไขซึ่งทำหน้าที่ในการดำเนินงานที่กำหนดเหล่านี้ จากนั้น ให้ดำเนินการเปลี่ยนแปลงต่อไปนี้เพื่ออำนวยความสะดวกในการดำเนินการเหล่านี้:
const resolvers = {
Mutation: {
register: async (_, { userInput: { name, password, role } }) => {
if (!name || !password || !role) {
throw new Error('Name password, and role required');
}
const newUser = new User({
name: name,
password: password,
role: role,
});
try {
const response = await newUser.save();
return {
id: response._id,
...response._doc,
};
} catch (error) {
console.error(error);
throw new Error('Failed to create user');
}
},
login: async (_, { name, password }) => {
try {
const user = await User.findOne({ name: name });
if (!user) {
throw new Error('User not found');
}
if (password !== user.password) {
throw new Error('Incorrect password');
}
const token = generateToken(user);
if (!token) {
throw new Error('Failed to generate token');
}
return {
message: 'Login successful',
token: token,
};
} catch (error) {
console.error(error);
throw new Error('Login failed');
}
}
},
การเปลี่ยนแปลงรีจิสเตอร์มีหน้าที่รับผิดชอบในการจัดการกระบวนการลงทะเบียนผ่านการเพิ่มข้อมูลผู้ใช้ใหม่ลงในฐานข้อมูล ในทางกลับกัน การเปลี่ยนแปลงการเข้าสู่ระบบเกี่ยวข้องกับการเข้าสู่ระบบของผู้ใช้ และเมื่อการตรวจสอบความถูกต้องสำเร็จ จะสร้าง JSON Web Token (JWT) ในขณะเดียวกันก็ส่งคืนข้อความแสดงความสำเร็จในการตอบกลับด้วย
เพื่อจำกัดการเข้าถึงแบบสอบถามที่ดึงข้อมูลผู้ใช้เฉพาะสำหรับผู้ใช้ที่ได้รับการตรวจสอบสิทธิ์และมีสิทธิ์ซึ่งมีบทบาท"ผู้ดูแลระบบ"จำเป็นอย่างยิ่งที่จะต้องรวมตรรกะการอนุญาตภายในระบบ สิ่งนี้ทำให้มั่นใจได้ว่าบุคคลที่ไม่ได้รับอนุญาตจะไม่สามารถเข้าถึงข้อมูลที่ละเอียดอ่อนได้
โดยพื้นฐานแล้ว การสอบสวนจะเริ่มต้นตรวจสอบความถูกต้องของโทเค็น ตามด้วยการประเมินบทบาทของผู้ใช้ ในกรณีที่การตรวจสอบการอนุญาตพิสูจน์ได้ว่าเป็นผลดี คำร้องขอการแก้ปัญหาจะต้องดึงข้อมูลและให้ข้อมูลของผู้ใช้ปลายทางจากพื้นที่เก็บข้อมูล
Query: {
users: async (parent, args, context) => {
try {
const token = context.req.headers.authorization || '';
const decodedToken = verifyToken(token);
if (decodedToken.role !== 'Admin') {
throw new ('Unauthorized. Only Admins can access this data.');
}
const users = await User.find({}, { name: 1, _id: 1, role:1 });
return users;
} catch (error) {
console.error(error);
throw new Error('Failed to fetch users');
}
},
},
};
สุดท้ายให้เริ่มเซิร์ฟเวอร์การพัฒนา:
node server.js
ขอแนะนำอย่างยิ่งให้คุณตรวจสอบความสามารถในการทำงานของ GraphQL API ของเราโดยใช้แซนด์บ็อกซ์ Apollo Server API ภายในเว็บเบราว์เซอร์ของคุณ เพื่ออธิบายกระบวนการนี้ ให้พิจารณาใช้การเปลี่ยนแปลง"การลงทะเบียน"เพื่อป้อนข้อมูลผู้ใช้ใหม่ลงในฐานข้อมูล ตามด้วยการเปลี่ยนแปลง"การเข้าสู่ระบบ"เพื่อวัตถุประสงค์ในการตรวจสอบสิทธิ์
สุดท้าย รวมโทเค็น JWT ไว้ในพารามิเตอร์ส่วนหัวการอนุญาต และดึงข้อมูลผู้ใช้จากฐานข้อมูลในภายหลังโดยดำเนินการค้นหา
การรักษาความปลอดภัย GraphQL API
การรับรองความถูกต้องและการอนุญาตมีบทบาทสำคัญในการรับรองความปลอดภัยของ GraphQL API อย่างไรก็ตาม การใช้งานเพียงอย่างเดียวไม่ได้รับประกันการป้องกันที่สมบูรณ์ จำเป็นอย่างยิ่งที่จะต้องรวมมาตรการความปลอดภัยเพิ่มเติม เช่น การตรวจสอบอินพุตที่แข็งแกร่งและการเข้ารหัสข้อมูลที่ละเอียดอ่อน เพื่อให้เกิดความปลอดภัยอย่างละเอียด
การใช้กลยุทธ์ความปลอดภัยที่ครอบคลุมทั้งหมดถือเป็นสิ่งสำคัญในการปกป้อง Application Programming Interfaces (API) ของคุณจากภัยคุกคามประเภทต่างๆ ที่อาจเกิดขึ้น