از معماری ساختاریافته Nest برای ایجاد APIهای امن و کارآمد REST استفاده کنید.
Express.js یک فناوری عالی برای ایجاد API های امن و قوی REST است، با این حال، ساختار از پیش تعریف شده ای ارائه نمی دهد. ماهیت مینیمالیستی آن به شما اجازه می دهد تا جنبه های ضروری مانند مسیریابی، سازماندهی کد و اقدامات امنیتی را به صورت دستی یا با استفاده از میان افزارها و کتابخانه های موجود انجام دهید.
در مقابل، Nest.js که بر روی Express.js و Node.js ساخته شده است، یک انتزاع سطح بالاتر را معرفی می کند که ساختاری واضح، رویکرد سازماندهی کد قوی و جزئیات پیاده سازی ساده را ارائه می دهد. اساساً، Nest.js معماری ساختارمندتری را برای ساختن APIها و سرویس های باطن کارآمد و ایمن ارائه می دهد.
راه اندازی یک پروژه Nest.js
برای شروع، ابتدا باید با اجرای دستور زیر، خط فرمان Nest.js (CLI) را به صورت سراسری نصب کنید:
npm i -g @nestjs/cli
پس از اتمام نصب، ادامه دهید و یک پروژه جدید با اجرای:
nest new nest-jwt-api
در مرحله بعد، Nest.js CLI از شما می خواهد که یک مدیر بسته برای نصب وابستگی ها انتخاب کنید. برای این آموزش، از npm، Node Package Manager استفاده خواهیم کرد. npm را انتخاب کنید و منتظر بمانید تا زمانی که CLI یک پروژه اساسی Nest.js ایجاد می کند و همه فایل های پیکربندی مورد نیاز و وابستگی های اولیه مورد نیاز برای اجرای برنامه را نصب می کند.
پس از راه اندازی پروژه، به دایرکتوری پروژه بروید و سرور توسعه را راه اندازی کنید.
cd nest-jwt-api
npm run start
در نهایت، دستور زیر را برای نصب بسته هایی که برای این پروژه استفاده خواهیم کرد، اجرا کنید.
npm install mongodb mongoose @nestjs/mongoose @types/bcrypt bcrypt jsonwebtoken @nestjs/jwt
می توانید کد این پروژه را در این مخزن GitHub پیدا کنید.
اتصال پایگاه داده MongoDB را پیکربندی کنید
یک پایگاه داده MongoDB را به صورت محلی تنظیم کنید یا یک خوشه MongoDB را روی ابر پیکربندی کنید. پس از راه اندازی پایگاه داده، رشته URI اتصال پایگاه داده را کپی کنید، یک فایل .env در دایرکتوری ریشه پوشه پروژه خود ایجاد کنید و در رشته اتصال قرار دهید:
MONGO_URI="connection string"
سپس، app.module.ts را در فایل دایرکتوری src به روز کنید تا Mongoose را به صورت زیر پیکربندی کنید:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserAuthModule } from './user-auth/user-auth.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true,
}),
MongooseModule.forRoot(process.env.MONGO_URI),
UserAuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
کد ارائه شده سه ماژول ضروری را برای برنامه Nest.js پیکربندی می کند: ConfigModule برای پیکربندی محیط، MongooseModule برای ایجاد اتصال MongoDB و UserAuthModule برای احراز هویت کاربر. لطفاً توجه داشته باشید که در این مرحله، ممکن است خطایی رخ دهد زیرا UserAuthModule هنوز تعریف نشده است، اما ما آن را در بخش بعدی ایجاد خواهیم کرد.
ایجاد ماژول احراز هویت کاربر
برای حفظ کد تمیز و منظم، با اجرای دستور زیر یک ماژول احراز هویت کاربر ایجاد کنید.
ماژول nest g user-auth
ابزار Nest.js CLI به طور خودکار فایل های ماژول مورد نیاز را تولید می کند. علاوه بر این، فایل app.module.ts را با تغییرات لازم مربوط به ماژول احراز هویت کاربر به روز می کند.
شما می توانید انتخاب کنید که فایل های پیکربندی پروژه اصلی را به صورت دستی ایجاد کنید، با این وجود، ابزار CLI با ایجاد خودکار موارد مورد نیاز، علاوه بر به روز رسانی تغییرات متناسب در فایل app.module.ts، این فرآیند را ساده می کند.
یک طرحواره کاربری ایجاد کنید
در پوشه user-auth جدید ایجاد شده در پوشه src، یک فایل schemas/user-auth.schema.ts جدید ایجاد کنید و کد زیر را برای ایجاد یک طرحواره Mongoose برای مدل User اضافه کنید.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ timestamps: true })
export class User {
@Prop()
username: string;
@Prop()
password: string;
}
export type UserDocument = User & Document;
export const UserSchema = SchemaFactory.createForClass(User);
ایجاد سرویس احراز هویت کاربر
اکنون، بیایید سرویس احراز هویت کاربر را ایجاد کنیم که با اجرای دستور زیر، منطق احراز هویت را برای REST API مدیریت می کند:
nest g service user-auth
این دستور یک فایل user-auth.service.ts در دایرکتوری user-ath ایجاد می کند. این فایل را باز کرده و با کد زیر به روز کنید.
- ابتدا واردات زیر را انجام دهید. وارد کردن { Injectable, NotFoundException, Logger, UnauthorizedException } از ‘@nestjs/common’;وارد کردن { InjectModel } از ‘@nestjs/mongoose’;وارد کردن { Model } از ‘mongoose’;وارد کردن { User } از ‘./schemas/user -auth.schema’;وارد کردن * به عنوان bcrypt از ‘bcrypt’;وارد کردن { JwtService } از ‘@nestjs/jwt’;
- سپس، یک کلاس UserAuthService ایجاد کنید که عملکرد ثبت نام کاربر، ورود به سیستم و بازیابی همه مسیرهای داده کاربر را در بر می گیرد.
import { Injectable, NotFoundException, Logger, UnauthorizedException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user-auth.schema';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UserAuthService {
private readonly logger = new Logger(UserAuthService.name);
constructor( @InjectModel(User.name) private userModel: Model<User>, private jwtService: JwtService) {}
async registerUser(username: string, password: string): Promise<{ message: string }> {
try {
const hash = await bcrypt.hash(password, 10);
await this.userModel.create({ username, password: hash });
return { message: 'User registered successfully' };
} catch (error) {
throw new Error('An error occurred while registering the user');
}
}
async loginUser(username: string, password: string): Promise<string> {
try {
const user = await this.userModel.findOne({ username });
if (!user) {
throw new NotFoundException('User not found');
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
throw new UnauthorizedException('Invalid login credentials');
}
const payload = { userId: user._id };
const token = this.jwtService.sign(payload);
return token;
} catch (error) {
console.log(error);
throw new UnauthorizedException('An error occurred while logging in');
}
}
async getUsers(): Promise<User[]> {
try {
const users = await this.userModel.find({});
return users;
} catch (error) {
this.logger.error(`An error occurred while retrieving users: ${error.message}`);
throw new Error('An error occurred while retrieving users');
}
}
}
کلاس UserAuthService منطق ثبت نام کاربر، ورود به سیستم و بازیابی اطلاعات کاربر را پیاده سازی می کند. از userModel برای تعامل با پایگاه داده و انجام اقدامات مورد نیاز از جمله هش کردن رمز عبور در حین ثبت نام، اعتبارسنجی اعتبار ورود به سیستم و در نهایت، تولید توکن های JWT پس از احراز هویت موفق استفاده می کند.
پیاده سازی گارد احراز هویت
برای اطمینان از امنیت منابع حساس، محدود کردن دسترسی منحصرا به کاربران مجاز بسیار مهم است. این امر با اجرای یک اقدام امنیتی که حضور یک JWT معتبر را در درخواستهای API بعدی برای نقاط پایانی محافظتشده، در این مورد، مسیر کاربران الزامی میکند، به دست میآید. در پوشه user-ath، یک فایل auth.guard.ts جدید ایجاد کنید و کد زیر را اضافه کنید.
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { secretKey } from './config';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: secretKey.secret,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
این کد یک محافظ، همانطور که در اسناد رسمی مشخص شده است، برای محافظت از مسیرها و اطمینان از اینکه فقط کاربران احراز هویت شده با توکن JWT معتبر می توانند به آنها دسترسی داشته باشند، پیاده سازی می کند.
توکن JWT را از هدر درخواست استخراج میکند، صحت آن را با استفاده از JwtService تأیید میکند و بار رمزگشایی شده را برای پردازش بیشتر به ویژگی درخواست[‘user’] اختصاص میدهد. اگر توکن مفقود یا نامعتبر باشد، یک UnauthorizedException را برای جلوگیری از دسترسی به مسیر محافظت شده پرتاب می کند.
حالا فایل config.ts را در همان دایرکتوری ایجاد کنید و کد زیر را اضافه کنید.
export const secretKey = {
secret: 'SECTRET VALUE.',
};
این کلید مخفی برای امضا و تأیید صحت JWT ها استفاده می شود. برای جلوگیری از دسترسی غیرمجاز و محافظت از یکپارچگی JWT ها، ذخیره مقدار کلید به طور ایمن ضروری است.
API Controller را تعریف کنید
یک کنترلر ایجاد کنید که نقاط انتهایی API را برای احراز هویت کاربر کنترل کند.
nest g controller user-auth
در مرحله بعد، کد ارائه شده در این فایل مخزن GitHub را کپی کنید و آن را به فایل user-auth.controller.ts اضافه کنید – این کد نقاط پایانی را برای ثبت نام کاربر، ورود به سیستم و بازیابی اطلاعات کاربر تعریف می کند. دکوراتور UseGuards (AuthGuard) برای اجرای احراز هویت برای نقطه پایانی getUsers گنجانده شده است و اطمینان حاصل می کند که فقط کاربران احراز هویت شده دسترسی دارند.
فایل user-auth.module.ts را به روز کنید
برای انعکاس تغییرات ایجاد شده در پروژه، فایل user-auth.module.ts را برای پیکربندی ماژول ها، سرویس ها و کنترلرهای لازم برای احراز هویت کاربر به روز کنید.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UserAuthController } from './user-auth.controller';
import { UserAuthService } from './user-auth.service';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './schemas/user-auth.schema';
import { secretKey } from './config';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
JwtModule.register({
secret: secretKey.secret,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [UserAuthController],
providers: [UserAuthService],
})
export class UserAuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
}
}
در نهایت، سرور توسعه را بچرخانید و نقاط پایانی API را با استفاده از Postman آزمایش کنید.
npm run start
ساخت Secure Nest.js REST API
ساختن API های امن Nest.js REST نیازمند یک رویکرد جامع است که فراتر از تکیه بر JWT ها برای احراز هویت و مجوز است. در حالی که JWT ها مهم هستند، اجرای اقدامات امنیتی اضافی نیز به همان اندازه ضروری است.
علاوه بر این، با اولویت دادن به امنیت در هر مرحله از توسعه API، می توانید از امنیت سیستم های باطن خود اطمینان حاصل کنید.