tags: nestjs, authentication, authorization, helme" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

18143453325 在線咨詢 在線咨詢
18143453325 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運(yùn)營 > 加固你的網(wǎng)站安全–nestjs文檔更新

加固你的網(wǎng)站安全–nestjs文檔更新

時間:2023-04-19 01:06:02 | 來源:網(wǎng)站運(yùn)營

時間:2023-04-19 01:06:02 來源:網(wǎng)站運(yùn)營

加固你的網(wǎng)站安全–nestjs文檔更新:在最新的Nestjs文檔中,作者將安全內(nèi)容作為單獨(dú)的章節(jié)撰寫,以強(qiáng)調(diào)網(wǎng)絡(luò)安全的重要性,依據(jù)官方文檔的做法,可以有效提高網(wǎng)站安全性。

tags: nestjs, authentication, authorization, helmet, cors, csrf, encryption, hashing

認(rèn)證(Authentication)

身份驗(yàn)證是大多數(shù)現(xiàn)有應(yīng)用程序的重要組成部分。有許多不同的方法、策略和方法來處理用戶授權(quán)。任何項(xiàng)目采用的方法取決于其特定的應(yīng)用程序要求。本章介紹了幾種可以適應(yīng)各種不同要求的身份驗(yàn)證方法。

passport 是目前最流行的 node.js 認(rèn)證庫,為社區(qū)所熟知,并相繼應(yīng)用于許多生產(chǎn)應(yīng)用中。將此工具與 Nest 框架集成起來非常簡單。為了演示,我們將設(shè)置 passport-http-bearer 和 passport-jwt 策略。

Passport是最流行的 node.js 身份驗(yàn)證庫,為社區(qū)所熟知,并成功地應(yīng)用于許多生產(chǎn)應(yīng)用程序中。將這個庫與使用 @nestjs/passport 模塊的 Nest 應(yīng)用程序集成起來非常簡單。在較高級別,Passport 執(zhí)行一系列步驟以:

身份認(rèn)證

讓我們充實(shí)一下我們的需求。對于此用例,客戶端將首先使用用戶名和密碼進(jìn)行身份驗(yàn)證。一旦通過身份驗(yàn)證,服務(wù)器將發(fā)出 JWT,該 JWT 可以在后續(xù)請求的授權(quán)頭中作為 token發(fā)送,以驗(yàn)證身份驗(yàn)證。我們還將創(chuàng)建一個受保護(hù)的路由,該路由僅對包含有效 JWT 的請求可訪問。

我們將從第一個需求開始:驗(yàn)證用戶。然后我們將通過發(fā)行 JWT 來擴(kuò)展它。最后,我們將創(chuàng)建一個受保護(hù)的路由,用于檢查請求上的有效 JWT 。

首先,我們需要安裝所需的軟件包。Passport 提供了一種名為 Passport-local 的策略,它實(shí)現(xiàn)了一種用戶名/密碼身份驗(yàn)證機(jī)制,這符合我們在這一部分用例中的需求。

$ npm install --save @nestjs/passport passport passport-local$ npm install --save-dev @types/passport-local對于您選擇的任何 Passport 策略,都需要 @nestjs/Passport 和 Passport 包。然后,需要安裝特定策略的包(例如,passport-jwt 或 passport-local),它實(shí)現(xiàn)您正在構(gòu)建的特定身份驗(yàn)證策略。此外,您還可以安裝任何 Passport策略的類型定義,如上面的 @types/Passport-local 所示,它在編寫 TypeScript 代碼時提供了幫助。

Passport 策略

現(xiàn)在可以實(shí)現(xiàn)身份認(rèn)證功能了。我們將首先概述用于任何 Passport 策略的流程。將 Passport 本身看作一個框架是有幫助的??蚣艿膬?yōu)雅之處在于,它將身份驗(yàn)證過程抽象為幾個基本步驟,您可以根據(jù)實(shí)現(xiàn)的策略對這些步驟進(jìn)行自定義。它類似于一個框架,因?yàn)槟梢酝ㄟ^提供定制參數(shù)(作為 JSON 對象)和回調(diào)函數(shù)( Passport 在適當(dāng)?shù)臅r候調(diào)用這些回調(diào)函數(shù))的形式來配置它。 @nestjs/passport 模塊將該框架包裝在一個 Nest 風(fēng)格的包中,使其易于集成到 Nest 應(yīng)用程序中。下面我們將使用 @nestjs/passport ,但首先讓我們考慮一下 vanilla Passport 是如何工作的。

在 vanilla Passport 中,您可以通過提供以下兩項(xiàng)配置策略:

  1. 組特定于該策略的選項(xiàng)。例如,在 JWT 策略中,您可以提供一個秘令來對令牌進(jìn)行簽名。
  2. “驗(yàn)證回調(diào)”,在這里您可以告訴 Passport 如何與您的用戶存儲交互(在這里您可以管理用戶帳戶)。在這里,驗(yàn)證用戶是否存在(或創(chuàng)建一個新用戶),以及他們的憑據(jù)是否有效。Passport 庫期望這個回調(diào)在驗(yàn)證成功時返回完整的用戶消息,在驗(yàn)證失敗時返回 null(失敗定義為用戶沒有找到,或者在使用 Passport-local 的情況下,密碼不匹配)。
    使用 @nestjs/passport ,您可以通過擴(kuò)展 PassportStrategy 類來配置 passport 策略。通過調(diào)用子類中的 super() 方法傳遞策略選項(xiàng)(上面第1項(xiàng)),可以選擇傳遞一個 options 對象。通過在子類中實(shí)現(xiàn) validate() 方法,可以提供verify 回調(diào)(上面第2項(xiàng))。我們將從生成一個 AuthModule 開始,其中有一個 AuthService :
$ nest g module auth$ nest g service auth當(dāng)我們實(shí)現(xiàn) AuthService 時,我們會發(fā)現(xiàn)在 UsersService 中封裝用戶操作是很有用的,所以現(xiàn)在讓我們生成這個模塊和服務(wù):

$ nest g module users$ nest g service users替換這些生成文件的默認(rèn)內(nèi)容,如下所示。對于我們的示例應(yīng)用程序,UsersService 只是在內(nèi)存中維護(hù)一個硬編碼的用戶列表,以及一個根據(jù)用戶名檢索用戶列表的 find 方法。在真正的應(yīng)用程序中,這是您使用選擇的庫(例如 TypeORM、Sequelize、Mongoose等)構(gòu)建用戶模型和持久層。

users/users.service.ts
import { Injectable } from '@nestjs/common';export type User = any;@Injectable()export class UsersService { private readonly users: User[]; constructor() { this.users = [ { userId: 1, username: 'john', password: 'changeme', }, { userId: 2, username: 'chris', password: 'secret', }, { userId: 3, username: 'maria', password: 'guess', }, ]; } async findOne(username: string): Promise<User | undefined> { return this.users.find(user => user.username === username); }}在 UsersModule 中,惟一需要做的更改是將 UsersService 添加到 @Module 裝飾器的 exports 數(shù)組中,以便提供給其他模塊外部可見(我們很快將在 AuthService 中使用它)。

users/users.module.ts
import { Module } from '@nestjs/common';import { UsersService } from './users.service';@Module({ providers: [UsersService], exports: [UsersService],})export class UsersModule {}我們的 AuthService 的任務(wù)是檢索用戶并驗(yàn)證密碼。為此,我們創(chuàng)建了 validateUser() 方法。在下面的代碼中,我們使用 ES6 擴(kuò)展操作符從 user 對象中提取 password 屬性,然后再返回它。稍后,我們將從 Passport 本地策略中調(diào)用 validateUser() 方法。

auth/auth.service.ts
import { Injectable } from '@nestjs/common';import { UsersService } from '../users/users.service';@Injectable()export class AuthService { constructor(private readonly usersService: UsersService) {} async validateUser(username: string, pass: string): Promise<any> { const user = await this.usersService.findOne(username); if (user && user.password === pass) { const { password, ...result } = user; return result; } return null; }}
當(dāng)然,在實(shí)際的應(yīng)用程序中,您不會以純文本形式存儲密碼。 取而代之的是使用帶有加密單向哈希算法的 bcrypt 之類的庫。使用這種方法,您只需存儲散列密碼,然后將存儲的密碼與輸入密碼的散列版本進(jìn)行比較,這樣就不會以純文本的形式存儲或暴露用戶密碼。為了保持我們的示例應(yīng)用程序的簡單性,我們違反了這個絕對命令并使用純文本。不要在真正的應(yīng)用程序中這樣做!
現(xiàn)在,我們更新 AuthModule 來導(dǎo)入 UsersModule 。
auth/auth.module.ts
import { Module } from '@nestjs/common';import { AuthService } from './auth.service';import { UsersModule } from '../users/users.module';@Module({ imports: [UsersModule], providers: [AuthService],})export class AuthModule {}現(xiàn)在我們可以實(shí)現(xiàn) Passport 本地身份驗(yàn)證策略。在auth文件夾中創(chuàng)建一個名為 local.strategy.ts 文件,并添加以下代碼:

auth/local.strategy.ts
import { Strategy } from 'passport-local';import { PassportStrategy } from '@nestjs/passport';import { Injectable, UnauthorizedException } from '@nestjs/common';import { AuthService } from './auth.service';@Injectable()export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private readonly authService: AuthService) { super(); } async validate(username: string, password: string): Promise<any> { const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } return user; }}我們遵循了前面描述的所有護(hù)照策略。在我們的 passport-local 用例中,沒有配置選項(xiàng),因此我們的構(gòu)造函數(shù)只是調(diào)用 super() ,沒有 options 對象。

我們還實(shí)現(xiàn)了 validate() 方法。對于每個策略,Passport 將使用適當(dāng)?shù)奶囟ㄓ诓呗缘囊唤M參數(shù)調(diào)用 verify 函數(shù)(使用 @nestjs/Passport 中的 validate() 方法實(shí)現(xiàn))。對于本地策略,Passport 需要一個具有以下簽名的 validate() 方法: validate(username: string, password: string): any。

大多數(shù)驗(yàn)證工作是在我們的 AuthService 中完成的(在 UserService 的幫助下),所以這個方法非常簡單。任何 Passport 策略的 validate() 方法都將遵循類似的模式,只是表示憑證的細(xì)節(jié)方面有所不同。如果找到了用戶并且憑據(jù)有效,則返回該用戶,以便 Passport 能夠完成其任務(wù)(例如,在請求對象上創(chuàng)建user 屬性),并且請求處理管道可以繼續(xù)。如果沒有找到,我們拋出一個異常,讓異常層處理它。

通常,每種策略的 validate() 方法的惟一顯著差異是如何確定用戶是否存在和是否有效。例如,在 JWT 策略中,根據(jù)需求,我們可以評估解碼令牌中攜帶的 userId 是否與用戶數(shù)據(jù)庫中的記錄匹配,或者是否與已撤銷的令牌列表匹配。因此,這種子類化和實(shí)現(xiàn)特定于策略驗(yàn)證的模式是一致的、優(yōu)雅的和可擴(kuò)展的。

我們需要配置 AuthModule 來使用剛才定義的 Passport 特性。更新 auth.module??雌饋硐襁@樣:

auth/auth.module.ts
import { Module } from '@nestjs/common';import { AuthService } from './auth.service';import { UsersModule } from '../users/users.module';import { PassportModule } from '@nestjs/passport';import { LocalStrategy } from './local.strategy';@Module({ imports: [UsersModule, PassportModule], providers: [AuthService, LocalStrategy],})export class AuthModule {}

內(nèi)置 Passport 守衛(wèi)

守衛(wèi)章節(jié)描述了守衛(wèi)的主要功能:確定請求是否由路由處理程序。這仍然是正確的,我們將很快使用這個標(biāo)準(zhǔn)功能。但是,在使用 @nestjs/passport 模塊的情況下,我們還將引入一個新的小問題,這個問題一開始可能會讓人感到困惑,現(xiàn)在讓我們來討論一下。從身份驗(yàn)證的角度來看,您的應(yīng)用程序可以以兩種狀態(tài)存在:

  1. 用戶/客戶端未登錄(未通過身份驗(yàn)證)
  2. 用戶/客戶端已登錄(已通過身份驗(yàn)證)
    在第一種情況下(用戶沒有登錄),我們需要執(zhí)行兩個不同的功能:

登錄路由

有了這個策略,我們現(xiàn)在就可以實(shí)現(xiàn)一個簡單的 /auth/login 路由,并應(yīng)用內(nèi)置的守衛(wèi)來啟動護(hù)照本地流。

打開 app.controller.ts 文件,并將其內(nèi)容替換為以下內(nèi)容:

app.controller.ts
import { Controller, Request, Post, UseGuards } from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';@Controller()export class AppController { @UseGuards(AuthGuard('local')) @Post('auth/login') async login(@Request() req) { return req.user; }}對于 @UseGuard(AuthGuard('local')),我們使用的是一個 AuthGuard ,它是在我們擴(kuò)展護(hù)照-本地策略時 @nestjs/passportautomatic 為我們準(zhǔn)備的。我們來分析一下。我們的 Passport 本地策略默認(rèn)名為"local" 。我們在 @UseGuards() 裝飾器中引用這個名稱,以便將它與護(hù)照本地包提供的代碼關(guān)聯(lián)起來。這用于消除在應(yīng)用程序中有多個 Passport 策略時調(diào)用哪個策略的歧義(每個策略可能提供一個特定于策略的 AuthGuard )。雖然到目前為止我們只有一個這樣的策略,但我們很快就會添加第二個,所以這是消除歧義所需要的。

為了測試我們的路由,我們將 /auth/login 路由簡單地返回用戶。這還允許我們演示另一個 Passport 特性: Passport 根據(jù)從 validate() 方法返回的值自動創(chuàng)建一個 user 對象,并將其作為 req.user 分配給請求對象。稍后,我們將用創(chuàng)建并返回 JWT 的代碼替換它。

因?yàn)檫@些是 API 路由,所以我們將使用常用的cURL庫來測試它們。您可以使用 UsersService 中硬編碼的任何用戶對象進(jìn)行測試。

$ # POST to /auth/login$ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"$ # result -> {"userId":1,"username":"john"}如果上述內(nèi)容可以正常工作,可以通過直接將策略名稱傳遞給AuthGuard()來引入代碼庫中的魔術(shù)字符串。作為替代,我們推薦創(chuàng)建自己的類,如下所示:

auth/local-auth.guard.ts
import { Injectable } from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';@Injectable()export class LocalAuthGuard extends AuthGuard('local') {}@UseGuards(LocalAuthGuard)@Post('auth/login')async login(@Request() req) { return req.user;}

JWT 功能

我們已經(jīng)準(zhǔn)備好進(jìn)入JWT部分的認(rèn)證系統(tǒng)。讓我們回顧并完善我們的需求:

$ npm install @nestjs/jwt passport-jwt$ npm install @types/passport-jwt --save-dev@nest/jwt 包是一個實(shí)用程序包,可以幫助 jwt 操作。passport-jwt 包是實(shí)現(xiàn) JWT 策略的 Passport包,@types/passport-jwt 提供 TypeScript 類型定義。

讓我們仔細(xì)看看如何處理 POST /auth/login 請求。我們使用護(hù)照本地策略提供的內(nèi)置AuthGuard 來裝飾路由。這意味著:

  1. 只有在了用戶之后,才會調(diào)用路由處理程序
  2. req參數(shù)將包含一個用戶屬性(在passport-local 身份驗(yàn)證流期間由 Passport 填充)
    考慮到這一點(diǎn),我們現(xiàn)在終于可以生成一個真正的 JWT ,并以這種方式返回它。為了使我們的服務(wù)保持干凈的模塊化,我們將在 authService 中生成 JWT 。在auth文件夾中添加 auth.service.ts 文件,并添加 login() 方法,導(dǎo)入JwtService ,如下圖所示:
auth/auth.service.ts
import { Injectable } from '@nestjs/common';import { UsersService } from '../users/users.service';import { JwtService } from '@nestjs/jwt';@Injectable()export class AuthService { constructor( private readonly usersService: UsersService, private readonly jwtService: JwtService ) {} async validateUser(username: string, pass: string): Promise<any> { const user = await this.usersService.findOne(username); if (user && user.password === pass) { const { password, ...result } = user; return result; } return null; } async login(user: any) { const payload = { username: user.username, sub: user.userId }; return { access_token: this.jwtService.sign(payload), }; }}我們使用 @nestjs/jwt 庫,該庫提供了一個 sign() 函數(shù),用于從用戶對象屬性的子集生成 jwt,然后以簡單對象的形式返回一個 access_token 屬性。注意:我們選擇 sub 的屬性名來保持我們的 userId 值與JWT 標(biāo)準(zhǔn)一致。不要忘記將 JwtService 提供者注入到 AuthService中。

現(xiàn)在,我們需要更新 AuthModule 來導(dǎo)入新的依賴項(xiàng)并配置 JwtModule 。

首先,在auth文件夾下創(chuàng)建 auth/constants.ts,并添加以下代碼:

auth/constants.ts
export const jwtConstants = { secret: 'secretKey',};我們將使用它在 JWT 簽名和驗(yàn)證步驟之間共享密鑰。

不要公開公開此密鑰。我們在這里這樣做是為了清楚地說明代碼在做什么,但是在生產(chǎn)系統(tǒng)中,您必須使用適當(dāng)?shù)拇胧﹣肀Wo(hù)這個密鑰,比如機(jī)密庫、環(huán)境變量或配置服務(wù)。

現(xiàn)在,在auth 文件夾下 auth.module.ts,并更新它看起來像這樣:

auth/auth.module.tsJSimport { Module } from '@nestjs/common';import { AuthService } from './auth.service';import { LocalStrategy } from './local.strategy';import { UsersModule } from '../users/users.module';import { PassportModule } from '@nestjs/passport';import { JwtModule } from '@nestjs/jwt';import { jwtConstants } from './constants';@Module({ imports: [ UsersModule, PassportModule, JwtModule.register({ secret: jwtConstants.secret, signOptions: { expiresIn: '60s' }, }), ], providers: [AuthService, LocalStrategy], exports: [AuthService],})export class AuthModule {}我們使用 register() 配置 JwtModule ,并傳入一個配置對象。有關(guān) Nest JwtModule 的更多信息請參見此處,有關(guān)可用配置選項(xiàng)的更多信息請參見此處。

現(xiàn)在我們可以更新 /auth/login 路徑來返回 JWT 。

app.controller.ts
import { Controller, Request, Post, UseGuards } from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';import { AuthService } from './auth/auth.service';@Controller()export class AppController { constructor(private readonly authService: AuthService) {} @UseGuards(AuthGuard('local')) @Post('auth/login') async login(@Request() req) { return this.authService.login(req.user); }}讓我們繼續(xù)使用 cURL 測試我們的路由。您可以使用 UsersService 中硬編碼的任何用戶對象進(jìn)行測試。

$ # POST to /auth/login$ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"$ # result -> {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}$ # Note: above JWT truncated

實(shí)施 Passport JWT

我們現(xiàn)在可以處理我們的最終需求:通過要求在請求時提供有效的 JWT 來保護(hù)端點(diǎn)。護(hù)照對我們也有幫助。它提供了用于用 JSON Web 標(biāo)記保護(hù) RESTful 端點(diǎn)的 passport-jwt 策略。在 auth 文件夾中 jwt.strategy.ts,并添加以下代碼:

auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';import { PassportStrategy } from '@nestjs/passport';import { Injectable } from '@nestjs/common';import { jwtConstants } from './constants';@Injectable()export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: jwtConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; }}對于我們的 JwtStrategy ,我們遵循了前面描述的所有 Passport 策略的相同配方。這個策略需要一些初始化,因此我們通過在 super() 調(diào)用中傳遞一個 options 對象來實(shí)現(xiàn)。您可以在這里閱讀關(guān)于可用選項(xiàng)的更多信息。在我們的例子中,這些選項(xiàng)是:

auth/auth.module.ts
import { Module } from '@nestjs/common';import { AuthService } from './auth.service';import { LocalStrategy } from './local.strategy';import { JwtStrategy } from './jwt.strategy';import { UsersModule } from '../users/users.module';import { PassportModule } from '@nestjs/passport';import { JwtModule } from '@nestjs/jwt';import { jwtConstants } from './constants';@Module({ imports: [ UsersModule, PassportModule, JwtModule.register({ secret: jwtConstants.secret, signOptions: { expiresIn: '60s' }, }), ], providers: [AuthService, LocalStrategy, JwtStrategy], exports: [AuthService],})export class AuthModule {}通過導(dǎo)入 JWT 簽名時使用的相同密鑰,我們可以確保 Passport 執(zhí)行的驗(yàn)證階段和 AuthService 執(zhí)行的簽名階段使用公共密鑰。

實(shí)現(xiàn)受保護(hù)的路由和 JWT 策略保護(hù),我們現(xiàn)在可以實(shí)現(xiàn)受保護(hù)的路由及其相關(guān)的保護(hù)。

打開 app.controller.ts 文件,更新如下:

app.controller.ts
import { Controller, Get, Request, Post, UseGuards } from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';import { AuthService } from './auth/auth.service';@Controller()export class AppController { constructor(private readonly authService: AuthService) {} @UseGuards(AuthGuard('local')) @Post('auth/login') async login(@Request() req) { return this.authService.login(req.user); } @UseGuards(AuthGuard('jwt')) @Get('profile') getProfile(@Request() req) { return req.user; }}同樣,我們將應(yīng)用在配置 passport-jwt 模塊時 @nestjs/passport 模塊自動為我們提供的 AuthGuard 。這個保護(hù)由它的默認(rèn)名稱 jwt 引用。當(dāng)我們請求GET /profile 路由時,保護(hù)程序?qū)⒆詣诱{(diào)用我們的 passport-jwt 自定義配置邏輯,驗(yàn)證 JWT ,并將用戶屬性分配給請求對象。

確保應(yīng)用程序正在運(yùn)行,并使用 cURL 測試路由。

$ # GET /profile$ curl http://localhost:3000/profile$ # result -> {"statusCode":401,"error":"Unauthorized"}$ # POST /auth/login$ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"$ # result -> {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm... }$ # GET /profile using access_token returned from previous step as bearer code$ curl http://localhost:3000/profile -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm..."$ # result -> {"userId":1,"username":"john"}注意,在 AuthModule 中,我們將 JWT 配置為 60 秒過期。這個過期時間可能太短了,而處理令牌過期和刷新的細(xì)節(jié)超出了本文的范圍。然而,我們選擇它來展示JWT 的一個重要品質(zhì)和 jwt 護(hù)照戰(zhàn)略。如果您在驗(yàn)證之后等待 60 秒再嘗試 GET /profile 請求,您將收到 401 未授權(quán)響應(yīng)。這是因?yàn)?Passport 會自動檢查 JWT 的過期時間,從而省去了在應(yīng)用程序中這樣做的麻煩。

我們現(xiàn)在已經(jīng)完成了 JWT 身份驗(yàn)證實(shí)現(xiàn)。JavaScript 客戶端(如 Angular/React/Vue )和其他 JavaScript 應(yīng)用程序現(xiàn)在可以安全地與我們的 API 服務(wù)器進(jìn)行身份驗(yàn)證和通信。在這里可以看到本節(jié)完整的程序代碼。

默認(rèn)策略

在我們的 AppController 中,我們在 @AuthGuard() 裝飾器中傳遞策略的名稱。我們需要這樣做,因?yàn)槲覀円呀?jīng)介紹了兩種 Passport 策略(護(hù)照本地策略和護(hù)照 jwt 策略),這兩種策略都提供了各種 Passport 組件的實(shí)現(xiàn)。傳遞名稱可以消除我們鏈接到的實(shí)現(xiàn)的歧義。當(dāng)應(yīng)用程序中包含多個策略時,我們可以聲明一個默認(rèn)策略,這樣如果使用該默認(rèn)策略,我們就不必在 @AuthGuard 裝飾器中傳遞名稱。下面介紹如何在導(dǎo)入 PassportModule 時注冊默認(rèn)策略。這段代碼將進(jìn)入 AuthModule :

要確定默認(rèn)策略行為,您可以注冊 PassportModule 。

auth.module.ts
import { Module } from '@nestjs/common';import { AuthService } from './auth.service';import { LocalStrategy } from './local.strategy';import { UsersModule } from '../users/users.module';import { PassportModule } from '@nestjs/passport';import { JwtModule } from '@nestjs/jwt';import { jwtConstants } from './constants';import { JwtStrategy } from './jwt.strategy';@Module({ imports: [ PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.register({ secret: jwtConstants.secret, signOptions: { expiresIn: '60s' }, }), UsersModule ], providers: [AuthService, LocalStrategy, JwtStrategy], exports: [AuthService],})export class AuthModule {}

請求范圍策略

passportAPI基于將策略注冊到庫的全局實(shí)例。因此策略并沒有設(shè)計(jì)為依賴請求的選項(xiàng)的或者根據(jù)每個請求動態(tài)生成實(shí)例(更多內(nèi)容見請求范圍提供者)。當(dāng)你配置你的策略為請求范圍時,Nest永遠(yuǎn)不會將其實(shí)例化,因?yàn)樗]有和任何特定路徑綁定。并沒有一個物理方法來決定哪個”請求范圍”策略會根據(jù)每個請求執(zhí)行。

然而,在策略中總有辦法動態(tài)處理請求范圍提供者。我們在這里利用模塊參考特性。

首先,打開local.strategy.ts文件并且將ModuleRef按照正常方法注入其中:

constructor(private moduleRef: ModuleRef){ super({ passReqToCallback:true; })}
注意: ModuleRef 類需要從@nestjs/core中導(dǎo)入。
要保證passReqToCallback屬性和上述示例中一樣配置為true。
在下一步中,請求的實(shí)例將被用于獲取一個當(dāng)前上下文標(biāo)識,而不是生成一個新的(更多關(guān)于請求上下文的內(nèi)容見這里)。
現(xiàn)在,在LocalStrategy類的validate()方法中,使用ContextIdFactory類中的getByRequest()方法來創(chuàng)建一個基于請求對向的上下文id,并將其傳遞給resolve()調(diào)用:
async validate( request: Request, username: string, password: string,) { const contextId = ContextIdFactory.getByRequest(request); // "AuthService" is a request-scoped provider const authService = await this.moduleRef.resolve(AuthService, contextId); ...}在上述例子中,resolve()方法會異步返回AuthService提供者的請求范圍實(shí)例(我們假設(shè)AuthService被標(biāo)示為一個請求范圍提供者)。

擴(kuò)展守衛(wèi)

在大多數(shù)情況下,使用一個提供的AuthGuard類是有用的。然而,在一些用例中你可能只是希望簡單地?cái)U(kuò)展默認(rèn)的錯誤處理或者認(rèn)證邏輯。在這種情況下,你可以通過一個子類來擴(kuò)展內(nèi)置的類并且覆蓋其方法。

import { ExecutionContext, Injectable, UnauthorizedException,} from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';@Injectable()export class JwtAuthGuard extends AuthGuard('jwt') { canActivate(context: ExecutionContext) { // 在這里添加自定義的認(rèn)證邏輯 // 例如調(diào)用 super.logIn(request) 來建立一個session return super.canActivate(context); } handleRequest(err, user, info) { // 可以拋出一個基于info或者err參數(shù)的異常 if (err || !user) { throw err || new UnauthorizedException(); } return user; }}

自定義 Passport

根據(jù)所使用的策略,Passport會采用一系列影響庫行為的屬性。使用 register() 方法將選項(xiàng)對象直接傳遞給Passport實(shí)例。例如:

PassportModule.register({ session: true });您還可以在策略的構(gòu)造函數(shù)中傳遞一個 options 對象來配置它們。至于本地策略,你可以通過例如:

constructor(private readonly authService: AuthService) { super({ usernameField: 'email', passwordField: 'password', });}看看Passport Website官方文檔吧。

命名策略

在實(shí)現(xiàn)策略時,可以通過向 PassportStrategy 函數(shù)傳遞第二個參數(shù)來為其提供名稱。如果你不這樣做,每個策略將有一個默認(rèn)的名稱(例如,”jwt”的 jwt策略 ):

export class JwtStrategy extends PassportStrategy(Strategy, 'myjwt')然后,通過一個像 @AuthGuard('myjwt') 這樣的裝飾器來引用它。

GraphQL

為了使用帶有 GraphQL 的 AuthGuard ,擴(kuò)展內(nèi)置的 AuthGuard 類并覆蓋 getRequest() 方法。

@Injectable()export class GqlAuthGuard extends AuthGuard('jwt') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context); return ctx.getContext().req; }}要使用上述結(jié)構(gòu),請確保在 GraphQL 模塊設(shè)置中將 request (req)對象作為上下文字的一部分傳遞:

GraphQLModule.forRoot({ context: ({ req }) => ({ req }),});要在 graphql 解析器中獲得當(dāng)前經(jīng)過身份驗(yàn)證的用戶,可以定義一個@CurrentUser()裝飾器:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';import { GqlExecutionContext } from '@nestjs/graphql';export const CurrentUser = createParamDecorator( (data: unknown, context: ExecutionContext) => { const ctx = GqlExecutionContext.create(context); return ctx.getContext().req.user; },);要在解析器中使用上述裝飾器,請確保將其作為查詢的參數(shù):

@Query(returns => User)@UseGuards(GqlAuthGuard)whoAmI(@CurrentUser() user: User) { return this.userService.findById(user.id);}

權(quán)限(Authorization)

權(quán)限是指確定一個用戶可以做什么的過程。例如,管理員用戶可以創(chuàng)建、編輯和刪除文章,非管理員用戶只能授權(quán)閱讀文章。

權(quán)限和認(rèn)證是相互獨(dú)立的。但是權(quán)限需要依賴認(rèn)證機(jī)制。

有很多方法和策略來處理權(quán)限。這些方法取決于其應(yīng)用程序的特定需求。本章提供了一些可以靈活運(yùn)用在不同需求條件下的權(quán)限實(shí)現(xiàn)方式。

基礎(chǔ)的RBAC實(shí)現(xiàn)

基于角色的訪問控制(RBAC)是一個基于角色和權(quán)限等級的中立的訪問控制策略。本節(jié)通過使用Nest守衛(wèi)來實(shí)現(xiàn)一個非常基礎(chǔ)的RBAC。

首先創(chuàng)建一個Role枚舉來表示系統(tǒng)中的角色:

role.enum.ts
export enum Role { User = 'user', Admin = 'admin',}
在更復(fù)雜的系統(tǒng)中,角色信息可能會存儲在數(shù)據(jù)庫里,或者從一個外部認(rèn)證提供者那里獲取。
然后,創(chuàng)建一個@Roles()的裝飾器,該裝飾器允許某些角色擁有獲取特定資源訪問權(quán)。
roles.decorator.ts
import { SetMetadata } from '@nestjs/common';import { Role } from '../enums/role.enum';export const ROLES_KEY = 'roles';export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);現(xiàn)在可以將@Roles()裝飾器應(yīng)用于任何路徑處理程序。

cats.controller.ts
@Post()@Roles(Role.Admin)create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto);}最后,我們創(chuàng)建一個RolesGuard類來比較當(dāng)前用戶擁有的角色和當(dāng)前路徑需要的角色。為了獲取路徑的角色(自定義元數(shù)據(jù)),我們使用Reflector輔助類,這是個@nestjs/core提供的一個開箱即用的類。

roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';import { Reflector } from '@nestjs/core';@Injectable()export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); }}
參見應(yīng)用上下文章節(jié)的反射與元數(shù)據(jù)部分,了解在上下文敏感的環(huán)境中使用Reflector的細(xì)節(jié)。
該例子被稱為“基礎(chǔ)的”是因?yàn)槲覀儍H僅在路徑處理層面檢查了用戶權(quán)限。在實(shí)際項(xiàng)目中,你可能有包含不同操作的終端/處理程序,它們各自需要不同的權(quán)限組合。在這種情況下,你可能要在你的業(yè)務(wù)邏輯中提供一個機(jī)制來檢查角色,這在一定程度上會變得難以維護(hù),因?yàn)槿狈σ粋€集中的地方來關(guān)聯(lián)不同的操作與權(quán)限。
在這個例子中,我們假設(shè)request.user包含用戶實(shí)例以及允許的角色(在roles屬性中)。在你的應(yīng)用中,需要將其與你的認(rèn)證守衛(wèi)關(guān)聯(lián)起來,參見認(rèn)證。
要確保該示例可以工作,你的User類看上去應(yīng)該像這樣:
class User { // ...other properties roles: Role[];}最后,在控制層或者全局注冊RolesGuard。

providers: [ { provide: APP_GUARD, useClass: RolesGuard, },],當(dāng)一個沒有有效權(quán)限的用戶訪問一個終端時,Nest自動返回以下響應(yīng):

{ "statusCode": 403, "message": "Forbidden resource", "error": "Forbidden"}
如果你想返回一個不同的錯誤響應(yīng),需要拋出特定異常來代替返回一個布爾值。

基于權(quán)利(Claims)的權(quán)限

一個身份被創(chuàng)建后,可能關(guān)聯(lián)來來自信任方的一個或者多個權(quán)利。權(quán)利是指一個表示對象可以做什么,而不是對象是什么的鍵值對。

要在Nest中實(shí)現(xiàn)基于權(quán)利的權(quán)限,你可以參考我們在RBAC部分的步驟,僅僅有一個顯著區(qū)別:比較許可(permissions)而不是角色。每個用戶應(yīng)該被授予了一組許可,相似地,每個資源/終端都應(yīng)該定義其需要的許可(例如通過專屬的@RequirePermissions()裝飾器)。

cats.controller.ts
@Post()@RequirePermissions(Permission.CREATE_CAT)create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto);}
在這個例子中,許可(和RBAC部分的角色類似)是一個TypeScript的枚舉,它包含了系統(tǒng)中所有的許可。

與CASL集成

CASL是一個權(quán)限庫,用于限制用戶可以訪問哪些資源。它被設(shè)計(jì)為可漸進(jìn)式增長的,從基礎(chǔ)權(quán)利權(quán)限到完整的基于主題和屬性的權(quán)限都可以實(shí)現(xiàn)。

首先,安裝@cacl/ability包:

$ npm i @casl/ability
在本例中,我們選擇CASL,但也可以根據(jù)項(xiàng)目需要選擇其他類似庫例如accesscontrol或者acl。
安裝完成后,為了說明CASL的機(jī)制,我們定義了兩個類實(shí)體,User和Article。
class User { id: number; isAdmin: boolean;}User類包含兩個屬性,id是用戶的唯一標(biāo)識,isAdmin代表用戶是否有管理員權(quán)限。

class Article { id: number; isPublished: boolean; authorId: string;}Article類包含三個屬性,分別是id、isPublished和authorId,id是文章的唯一標(biāo)識,isPublished代表文章是否發(fā)布,authorId代表發(fā)表該文章的用戶id。

接下來回顧并確定本示例中的需求:

export enum Action { Manage = 'manage', Create = 'create', Read = 'read', Update = 'update', Delete = 'delete',}
manage是CASL的關(guān)鍵詞,代表任何操作。
要封裝CASL庫,需要創(chuàng)建CaslModule和CaslAbilityFactory。
$ nest g module casl$ nest g class casl/casl-ability.factory創(chuàng)建完成后,在CaslAbilityFactory中定義creteForUser()方法。該方法將為用戶創(chuàng)建Ability對象。

type Subjects = typeof Article | typeof User | Article | User | 'all';export type AppAbility = Ability<[Action, Subjects]>;@Injectable()export class CaslAbilityFactory { createForUser(user: User) { const { can, cannot, build } = new AbilityBuilder< Ability<[Action, Subjects]> >(Ability as AbilityClass<AppAbility>); if (user.isAdmin) { can(Action.Manage, 'all'); // read-write access to everything } else { can(Action.Read, 'all'); // read-only access to everything } can(Action.Update, Article, { authorId: user.id }); cannot(Action.Delete, Article, { isPublished: true }); return build(); }}
all是CASL的關(guān)鍵詞,代表任何對象。
Ability,AbilityBuilder,和AbilityClass從@casl/ability包中導(dǎo)入。
在上述例子中,我們使用AbilityBuilder創(chuàng)建了Ability實(shí)例,如你所見,can和cannot接受同樣的參數(shù),但代表不同含義,can允許對一個對象執(zhí)行操作而cannot禁止操作,它們各能接受4個參數(shù),參見CASL文檔。
最后,將CaslAbilityFactory添加到提供者中,并在CaslModule模塊中導(dǎo)出。
import { Module } from '@nestjs/common';import { CaslAbilityFactory } from './casl-ability.factory';@Module({ providers: [CaslAbilityFactory], exports: [CaslAbilityFactory],})export class CaslModule {}現(xiàn)在,只要將CaslModule引入對象的上下文中,就可以將CaslAbilityFactory注入到任何標(biāo)準(zhǔn)類中。

constructor(private caslAbilityFactory: CaslAbilityFactory) {}在類中使用如下:

const ability = this.caslAbilityFactory.createForUser(user);if (ability.can(Action.Read, 'all')) { // "user" has read access to everything}
Ability類更多細(xì)節(jié)參見CASL 文檔。
例如,一個非管理員用戶,應(yīng)該可以閱讀文章,但不允許創(chuàng)建一篇新文章或者刪除一篇已有文章。
const user = new User();user.isAdmin = false;const ability = this.caslAbilityFactory.createForUser(user);ability.can(Action.Read, Article); // trueability.can(Action.Delete, Article); // falseability.can(Action.Create, Article); // false
雖然Ability和AlbilityBuilder類都提供can和cannot方法,但其目的并不一樣,接受的參數(shù)也略有不同。
依照我們的需求,一個用戶應(yīng)該能更新自己的文章。
const user = new User();user.id = 1;const article = new Article();article.authorId = user.id;const ability = this.caslAbilityFactory.createForUser(user);ability.can(Action.Update, article); // truearticle.authorId = 2;ability.can(Action.Update, article); // false如你所見,Ability實(shí)例允許我們通過一種可讀的方式檢查許可。AbilityBuilder采用類似的方式允許我們定義許可(并定義不同條件)。查看官方文檔了解更多示例。

進(jìn)階:通過策略守衛(wèi)的實(shí)現(xiàn)

本節(jié)我們說明如何聲明一個更復(fù)雜的守衛(wèi),用來配置在方法層面(也可以配置在類層面)檢查用戶是否滿足權(quán)限策略。在本例中,將使用CASL包進(jìn)行說明,但它并不是必須的。同樣,我們將使用前節(jié)創(chuàng)建的CaslAbilityFactory提供者。

首先更新我們的需求。目的是提供一個機(jī)制來檢查每個路徑處理程序的特定權(quán)限。我們將同時支持對象和方法(分別針對簡易檢查和面向函數(shù)式編程的目的)。

從定義接口和策略處理程序開始。

import { AppAbility } from '../casl/casl-ability.factory';interface IPolicyHandler { handle(ability: AppAbility): boolean;}type PolicyHandlerCallback = (ability: AppAbility) => boolean;export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;如上所述,我們提供了兩個可能的定義策略處理程序的方式,一個對象(實(shí)現(xiàn)了IPolicyHandle接口的類的實(shí)例)和一個函數(shù)(滿足PolicyHandlerCallback類型)。

接下來創(chuàng)建一個@CheckPolicies()裝飾器,該裝飾器允許配置訪問特定資源需要哪些權(quán)限。

export const CHECK_POLICIES_KEY = 'check_policy';export const CheckPolicies = (...handlers: PolicyHandler[]) => SetMetadata(CHECK_POLICIES_KEY, handlers);現(xiàn)在創(chuàng)建一個PoliciesGuard,它將解析并執(zhí)行所有和路徑相關(guān)的策略程序。

@Injectable()export class PoliciesGuard implements CanActivate { constructor( private reflector: Reflector, private caslAbilityFactory: CaslAbilityFactory, ) {} async canActivate(context: ExecutionContext): Promise<boolean> { const policyHandlers = this.reflector.get<PolicyHandler[]>( CHECK_POLICIES_KEY, context.getHandler(), ) || []; const { user } = context.switchToHttp().getRequest(); const ability = this.caslAbilityFactory.createForUser(user); return policyHandlers.every((handler) => this.execPolicyHandler(handler, ability), ); } private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) { if (typeof handler === 'function') { return handler(ability); } return handler.handle(ability); }}
在本例中,我們假設(shè)request.user包含了用戶實(shí)例。在你的應(yīng)用中,可能將其與你自定義的認(rèn)證守衛(wèi)關(guān)聯(lián)。參見認(rèn)證章節(jié)。
我們分析一下這個例子。policyHandlers是一個通過@CheckPolicies()裝飾器傳遞給方法的數(shù)組,接下來,我們用CaslAbilityFactory#create方法創(chuàng)建Ability對象,允許我們確定一個用戶是否擁有足夠的許可去執(zhí)行特定行為。我們將這個對象傳遞給一個可能是函數(shù)或者實(shí)現(xiàn)了IPolicyHandler類的實(shí)例的策略處理程序,暴露出handle()方法并返回一個布爾量。最后,我們使用Array#every方法來確保所有處理程序返回true。
為了測試這個守衛(wèi),我們綁定任意路徑處理程序,并且注冊一個行內(nèi)的策略處理程序(函數(shù)實(shí)現(xiàn)),如下:
@Get()@UseGuards(PoliciesGuard)@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Article))findAll() { return this.articlesService.findAll();}我們也可以定義一個實(shí)現(xiàn)了IPolicyHandler的類來代替函數(shù)。

export class ReadArticlePolicyHandler implements IPolicyHandler { handle(ability: AppAbility) { return ability.can(Action.Read, Article); }}并這樣使用。

@Get()@UseGuards(PoliciesGuard)@CheckPolicies(new ReadArticlePolicyHandler())findAll() { return this.articlesService.findAll();}
由于我們必須使用 new關(guān)鍵詞來實(shí)例化一個策略處理函數(shù),CreateArticlePolicyHandler類不能使用注入依賴。這在ModuleRef#get方法中強(qiáng)調(diào)過,參見這里)?;旧希娲ㄟ^@CheckPolicies()裝飾器注冊函數(shù)和實(shí)例,你需要允許傳遞一個Type<IPolicyHandler>,然后在守衛(wèi)中使用一個類型引用(moduleRef.get(YOUR_HANDLER_TYPE)獲取實(shí)例,或者使用ModuleRef#create方法進(jìn)行動態(tài)實(shí)例化。

加密和散列

加密是一個信息編碼的過程。這個過程將原始信息,即明文,轉(zhuǎn)換為密文。理想情況下,只有授權(quán)方可以將密文解密為明文。加密本身并不能防止干擾,但是會將可理解內(nèi)容拒絕給一個可能的攔截器。加密是個雙向的函數(shù),包含加密以及使用正確的key解密。

哈希是一個將給定值轉(zhuǎn)換成另一個值的過程。哈希函數(shù)使用數(shù)學(xué)算法來創(chuàng)建一個新值。一旦哈希完成,是無法從輸出值計(jì)算回輸入值的。

加密

Node.js提供了一個內(nèi)置的crypto模塊可用于加密和解密字符串,數(shù)字,Buffer,流等等。Nest未在此基礎(chǔ)上提供額外的包以減少不必要的干擾。

一個使用AES(高級加密系統(tǒng)) aes-256-ctr算法,CTR加密模式。

import { createCipheriv, randomBytes } from 'crypto';import { promisify } from 'util';const iv = randomBytes(16);const password = 'Password used to generate key';// The key length is dependent on the algorithm.// In this case for aes256, it is 32 bytes.const key = (await promisify(scrypt)(password, 'salt', 32)) as Buffer;const cipher = createCipheriv('aes-256-ctr', key, iv);const textToEncrypt = 'Nest';const encryptedText = Buffer.concat([ cipher.update(textToEncrypt), cipher.final(),]);接下來,解密encryptedText值。

import { createDecipheriv } from 'crypto';const decipher = createDecipheriv('aes-256-ctr', key, iv);const decryptedText = Buffer.concat([ decipher.update(encryptedText), decipher.final(),]);

散列

散列方面推薦使用 bcrypt 或 argon2包. Nest自身并未提供任何這些模塊的包裝器以減少不必要的抽象(讓學(xué)習(xí)曲線更短)。

例如,使用bcrypt來哈希一個隨機(jī)密碼。

首先安裝依賴。

$ npm i bcrypt$ npm i -D @types/bcrypt依賴安裝后,可以使用哈希函數(shù)。

import * as bcrypt from 'bcrypt';const saltOrRounds = 10;const password = 'random_password';const hash = await bcrypt.hash(password, saltOrRounds);使用genSalt函數(shù)來生成哈希需要的鹽。

const salt = await bcrypt.genSalt();使用compare函數(shù)來比較/檢查密碼。

const isMatch = await bcrypt.compare(password, hash);更多函數(shù)參見這里。

Helmet

通過適當(dāng)?shù)卦O(shè)置 HTTP 頭,Helmet 可以幫助保護(hù)您的應(yīng)用免受一些眾所周知的 Web 漏洞的影響。通常,Helmet 只是14個較小的中間件函數(shù)的集合,它們設(shè)置與安全相關(guān)的 HTTP 頭(閱讀更多)。

要在全局使用Helmet,需要在調(diào)用app.use()之前或者可能調(diào)用app.use()函數(shù)之前注冊。這是由平臺底層機(jī)制中(EXpress或者Fastify)中間件/路徑的定義決定的。如果在定義路徑之后使用helmet或者cors中間件,其之前的路徑將不會應(yīng)用這些中間件,而僅在定義之后的路徑中應(yīng)用。

在Express中使用(默認(rèn))

首先,安裝所需的包:

$ npm i --save helmet安裝完成后,將其應(yīng)用為全局中間件。

import * as helmet from 'helmet';// somewhere in your initialization fileapp.use(helmet());
如果在引入helmet時返回This expression is not callable錯誤。你可能需要將項(xiàng)目中tsconfig.json文件的allowSyntheticDefaultImports和esModuleInterop選項(xiàng)配置為true。在這種情況下,將引入聲明修改為:import helmet from 'helmet'。

在Fastify中使用

如果使用FastifyAdapter,安裝fastify-helmet包:

$ npm i --save fastify-helmetfastify-helmet需要作為Fastify插件而不是中間件使用,例如,用app.register()調(diào)用。

import * as helmet from 'fastify-helmet';// somewhere in your initialization fileapp.register(helmet);
在使用apollo-server-fastify和fastify-helmet時,在GraphQL應(yīng)用中與CSP使用時可能出問題,需要如下配置CSP。
app.register(helmet, { contentSecurityPolicy: { directives: { defaultSrc: [`'self'`], styleSrc: [`'self'`, `'unsafe-inline'`, 'cdn.jsdelivr.net', 'fonts.googleapis.com'], fontSrc: [`'self'`, 'fonts.gstatic.com'], imgSrc: [`'self'`, 'data:', 'cdn.jsdelivr.net'], scriptSrc: [`'self'`, `https: 'unsafe-inline'`, `cdn.jsdelivr.net`], }, },});// If you are not going to use CSP at all, you can use this:app.register(helmet, { contentSecurityPolicy: false,});

CORS

跨源資源共享(CORS)是一種允許從另一個域請求資源的機(jī)制。在底層,Nest 使用了Express的cors 包,它提供了一系列選項(xiàng),您可以根據(jù)自己的要求進(jìn)行自定義。

開始

為了啟用 CORS,必須調(diào)用 enableCors() 方法。

const app = await NestFactory.create(ApplicationModule);app.enableCors();await app.listen(3000);enableCors()方法使用一個可選的配置對象參數(shù)。該對象的可用屬性在其官方CORS文檔中有所描述。

可選地,通過create()方法的選項(xiàng)對象使能CORS,設(shè)置cors屬性為true來使能CORS的默認(rèn)屬性??蛇x地,傳遞一個CORS配置對象作為cors屬性值來自定義其行為:

const app = await NestFactory.create(ApplicationModule, { cors: true });await app.listen(3000);

CSRF保護(hù)

跨站點(diǎn)請求偽造(稱為 CSRF 或 XSRF)是一種惡意利用網(wǎng)站,其中未經(jīng)授權(quán)的命令從 Web 應(yīng)用程序信任的用戶傳輸。要減輕此類攻擊,您可以使用 csurf 軟件包。

在Express中使用(默認(rèn))

首先,安裝所需的包:

$ npm i --save csurf
正如 csurf 中間件頁面所解釋的,csurf 模塊需要首先初始化會話中間件或 cookie 解析器。有關(guān)進(jìn)一步說明,請參閱該文檔。
安裝完成后,將其應(yīng)用為全局中間件。
import * as csurf from 'csurf';// somewhere in your initialization fileapp.use(csurf());

在Fastify中使用

首先,安裝所需的包:

$ npm i --save fastify-csrf安裝完成后,將其注冊為fastify-csrf插件。

import fastifyCsrf from 'fastify-csrf';// somewhere in your initialization fileapp.register(fastifyCsrf);

限速

為了保護(hù)您的應(yīng)用程序免受暴力攻擊,您必須實(shí)現(xiàn)某種速率限制。幸運(yùn)的是,NPM上已經(jīng)有很多各種中間件可用。其中之一是express-rate-limit。

$ npm i --save express-rate-limit安裝完成后,將其應(yīng)用為全局中間件。

import * as rateLimit from 'express-rate-limit';// somewhere in your initialization fileapp.use( rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs }),);如果在服務(wù)器和以太網(wǎng)之間存在負(fù)載均衡或者反向代理,Express可能需要配置為信任proxy設(shè)置的頭文件,從而保證最終用戶得到正確的IP地址。要如此,首先使用NestExpressApplication平臺接口來創(chuàng)建你的app實(shí)例,然后配置trust proxy設(shè)置。

const app = await NestFactory.create<NestExpressApplication>(AppModule);// see https://expressjs.com/en/guide/behind-proxies.htmlapp.set('trust proxy', 1);
如果使用 FastifyAdapter,用 fastify-rate-limit替換。

關(guān)鍵詞:更新,安全

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉