Authentication
認證 是大多數應用程序中非常重要的部分. 有很多不同的方法和策略去處理 認證, 根據不同的要求決定。
本章節展示了幾種不同方式,這些方式通常是能夠適用於大多數情況的。
Passport 是node.js 中最流行的 用於認證處理邏輯的 庫,在社區範圍廣爲人知,並被應用於很多生產應用。 Next.js 也專門封裝了 @nestjs/passport
module, 用於簡單快速的整合 Nestjs 應用。 在較高的層級上來看,Passport 執行了一系列的步驟:
- 通過用戶的 “credentials" (例如, 用戶名/密碼,JSON Web Token( JWT ), 或者由認證提供商提供的 identity token 標識token) 的校驗來認證一個用戶。
- 管理 已經認證的狀態 (通過發佈一個可移植令牌(如JWT)或創建一個Express會話)。
- 將有關身份驗證的用戶信息附加到請求對象,以在路由處理程序中進一步使用。
Passport 有着豐富的 策略(strategies ) 生態,這些策略實現了各種 認證 機制。 你可以根據你的需要選擇各種策略, Passport 將上述的這些不同的步驟 抽象爲標準的 模式(pattern),且 @nestjs/passport
的作用就是將 這個 模式(pattern) 包裝並序列化成更熟悉的 Nest 的結構。
在本章節中,我們將會利用這些強大,靈活的模塊,爲一個 RESTful API 服務實現一個完整的 end-to-end 認證解決方案。 你可以使用本章中所描述的各種概念去實現任何 Passport 策略以實現自定義你的驗證邏輯。 你可以跟着這些步驟去構建這個完整的示例,也可以在這裏看到完整的示例代碼 here.
Authentication requirements
在這裏用例中,客戶端將會發送 username 和 password, 一旦認證完成, 這個服務器將會頒發一個 JWT 用以在客戶端後續的請求中,作爲 bearer token in an authorization header (請求頭中的 bearer token) 攜帶到服務端,以作爲認證成功的憑證。 我們還會創建一個受保護的路由,該路由將只能夠被攜帶 有效 JWT 的請求訪問。
我們將會從驗證一個用戶開始,然後將會擴展實現JWT 頒發,最後我們將會創建一個受保護的路由。
首先,先安裝需要的依賴包:
passport-local
: 實現了一個 username/password 驗證機制,很適合我們用例的這一部分需求。
$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
📔 對於你所選擇的 任何 Passsport 策略,你始終都會需要
@nestjs/passport
和passport
這兩個包依賴。 然後,你需要安裝安裝策略對應的包,這些包實現了了特定的認證策略(例如passport-jwt
, 或者passport-local
)。 此外,你也可以爲任何 Passport 策略安裝類型聲明文件@types/passport-local
,這將會提供代碼提示。
實現 Passport 策略
我們將會先從一個所有 Passport 策略都通用的過程開始。把 Passport 視作一個迷你框架將會比較有幫助。 該框架的優雅之處在於,它將身份驗證過程抽象爲幾個基本步驟,您可以根據所實現的策略自定義這些步驟。它很像一個框架是因爲你通過提供自定義的參數(作爲 plain JSON 對象)和 Passport 將會在合適時機執行的自定義回調函數進行配置。@nestjs/passport
module 將這個框架包裝包裝成爲一個 Nest 風格的依賴包,使得他易於整合到 Nest 應用。 我們接下來將會使用 @nestjs/passport
, 但是在此之前,我們先思考一下 原生 的 Passport 是如何工作的。
在原生 Passport 中, 你可以配置一個策略,通過提供兩個東西:
- 一系列的配置項用於特定的策略,例如,在 JWT策略中,我可能需要提供一個
secret
去簽名 token - 一個 "verify callback"(驗證回調), 這是你用於告訴 Passport 如何和你的 user store(你管理用戶賬戶的地方) 進行通信。 在這裏,你將會驗證一個用戶是否存在,以及他們的 credentials 憑證是否是有效的。 Passport 這個庫 期望 驗證成功時這個回調將返回一個完整的用戶(a full user), 失敗則返回
null
(包括了用戶不存在,密碼錯誤)
通過 @nestjs/passport
, 你可以通過 繼承 PassportStrategy
這個類以配置 Passport 策略。 你可以在你的子類中通過 super()
傳遞 策略配置項(上述item1), 通過重寫 validate()
方法提供你自己的 verify callback(上述item2)。
我們將會從 創建 AuthModule
,AuthService
開始:
$ nest g module auth
$ nest g service auth
當我們實現 AuthService
的時候,我們會發現在UsersService中封裝用戶操作非常有用,所以現在讓我們生成該模塊和服務:
$ nest g module users
$ nest g service users
將自動生成的內容替換成以下內容。對於我們這個簡單的應用, UserService
簡單的維護了一份硬編碼的用戶列表。 以及一個 find 方法用於 檢索到匹配的用戶,在實際的應用中。 這裏用於和數據庫做交互。
//users/users.service.ts
import { Injectable } from '@nestjs/common';
// This should be a real class/interface representing a user entity
export type User = any;
@Injectable()
export class UsersService {
private readonly users = [
{
userId: 1,
username: 'john',
password: 'changeme',
},
{
userId: 2,
username: 'maria',
password: 'guess',
},
];
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
在 UserModal
中,唯一的修改,是需要添加UserService
到 @Module
修飾器的 exports 數組,這樣,就能夠在module之外訪問到這個 UserModal
了。(我們很快就會用到了)
//users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
+ exports: [UsersService],
})
export class UsersModule {}
在我們的 AuthService
,我們創建了一個 validateUser()
方法以實現 檢索匹配用戶以及驗證密碼的功能。 在下方的代碼,我們使用了 ES6 的擴展運算符用於從用戶對象中剝離密碼屬性,然後再返回它。 我們稍後將會在 Passport local strtegy 本地策略中去調用這個 validateUser()
方法。
//auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(private 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;
}
}
⚠️
當然,在實際的應用中,你不會將密碼存儲爲明文,你或許可以使用一個叫做 bcrypt 的庫,它有着單向哈希鹽值算法。 通過這種方式,你就可以僅存儲哈希計算後的密碼。 然後將客戶端用戶提交上來的密碼同樣哈希計算後和數據庫存儲的哈希密碼比對以驗證正確性。 因此,永遠不要存儲以及暴露用戶的祕密爲明文。 爲了保持我們用例的簡潔性,我們這裏直接用明文。
現在,我們更新我們的 AuthModule
,引入UserModule
//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 {}
實現 Passport local (實現Passport 本地策略)
現在我們可以實現我們 Passport 的本地認證策略了 (local authentication strategy)。創建一個名爲 local.strategy.ts
的文件在 auth
目錄下。 並添加如下代碼:
//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 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;
}
}
我們遵循了先前爲所有 Passport 策略所描述的要素。 在我們的例子中,我們直接使用了 passport-local, 它並沒有提供 配置選項(configutaion options), 所以我們的構造器僅簡單的調用super()
,沒有提供配置選項。
📔
我們前面也說過了,我們可以在繼承 PassportStrategy 的LocalStrategy 子類的構造器中,通過調用
super()
方法,並向其傳遞配置選項以自定義Passport 策略的行爲。 在這個示例中,passport-local 策略默認期望接受 請求體(request body) 中名爲username
以及password
的屬性。 可以通過傳遞一個選項對象以指定需要的屬性名,例如:super({usernameField:"email"})
。 需要更多相關信息可以參考 Passport documentation 。
我們也實現了 valiated()
方法,對於每個策略, Passport 將會以特定的策略所指定的一組參數,將這個方法作爲驗證函數調用。對於我們這裏的 local-strategy, Passport 期望 validate()
方法有着這些簽名 :validate(username:string, password:string):any
。
多數的驗證工作在我們的 AuthService
中已經完成了(在UserService
的幫助下,因爲我們實際的方法實現是在這裏面),所以這個方法是相當的直接了當。 任何 Passport 策略的 validate()
方法都會尊從一個簡單的 pattern(模板/模式), 只是在表示憑據(credentials)的細節上有所不同。如果一個用戶匹配到了,並且這個憑據是有效的。 這個用戶將會返回,因此 Password 也將完成其工作。請求的Handling 管道流也會繼續。如果沒有匹配用戶,我們將會拋出一個 exception, 並讓我們的 exception 曾處理它。
@jayce: 可以將認證視作門衛,請求就像是送外賣,門衛的作用只是驗證小哥身份,驗證通過,小哥就會繼續工作。
通常,每個 策略 中的 validate()
方法,最顯著的不同之處在於,你是如何驗證用戶是不是存在,以及憑據是否有效。 例如, 在 JWT 策略中,取決於需求,我們可能會計算 解碼後的 token 中攜帶的 userId
是否能和我們數據庫中的用戶記錄匹配,或者匹配撤銷令牌的列表。 因此,這種子類化和實現特定策略驗證的模式是一致的、優雅的和可擴展的。
我們需要配置我們的 AuthModule
去使用我們剛纔定義的 Passport features(功能/能力/特性)。 將 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],
+ imports: [UsersModule, PassportModule],
- providers: [AuthService],
+ providers: [AuthService, LocalStrategy],
})
export class AuthModule {}
內置的 Passport 守衛(Guards)
Guards 章節描述了 Guards 的主要功能: 用於判定一個請求是否能夠被 router handler 所處理(handled), 在本章節中,這仍然是沒錯的, 我們也很快將會用到這個標準的能力。 不過,在涉及@nestjs/passport
的時候, 我們將會介紹一個可能一開始會讓人感到困惑的新問題。 因此,讓我們現在來討論一下。 思考一下,你的應用可能會存在兩種狀態。從一個認證(authentication)的層面來看:
- 用戶沒有登入 ( 沒有被認證 )
- 用戶登入了 ( 被認證了 )
在第一種情況中,也就是用戶沒登入時,我們需要執行兩個不同的功能:
- 限制未經認證的用戶能夠直接訪問的路由 (這裏的路由指的是後端API 的 url path)。 我們將會使用 Guards 實現這個功能。 通過在 需要被保護的 路由上方放置一個 Guard, 如你所料,我們將會在這個守衛中檢查 JWT 的有效性存在。 因此,我們稍後將會實現它。
- 當以前未經身份驗證的用戶試圖登陸的時候,啓動身份驗證步驟本身。 這一步中,我們將向有效用戶頒發 JWT。 想一想,我們知道我們需要一個裹挾着
username/password
憑證的 POST 請求去啓動認證。 因此我們將會設定一個POST /auth/login
的路由去handle這個請求。 這也引發了一個進一步的問題,我們的passport-local 策略究竟如何被觸發 ?
答案是很直接的: 通過使用另外一個,稍有不同的類型的 Guard。 @nestjs/passport
module 提供給我們了一個內置的 Guard 用以幫助我們完成該工作。 該Guard 將會觸發 Passport 策略,並開始執行上面描述的步驟 (檢查匹配用戶賬戶密碼,執行驗證函數(verify function),創建 user
屬性, 等等)
上面列舉的第二種情況(已登錄用戶) 僅僅依賴於我們已經討論過的標準Guard類型,以允許已登錄用戶訪問受保護的路由。
Login route 登錄路由
現在有了 策略 ,我們現在可以實現一個基本的 /auth/login
路由,並且應用內置的 Guard 以啓動 passport-local 流程。
打開 app.controller.ts
這個文件,並且替換爲以下內容:
//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;
}
}
沒有
app.controller.ts
這個文件,那麼可以通過nest g controller app
快速生成。
在 @UseGuards(AuthGuard('local'))
中,我們使用了 AuthGuard
, 通過傳入一個"local"
字符串,他將自動匹配到我們Passport 的 local 策略。它將自動和策略中的代碼關聯起來。
爲了測試我們的路由,我們現在只需要 /auth/login
路由返回用戶。 這還可以讓我們演示另一個 Passport 特性: Passport 根據 我們從 validate()
方法中返回的值, Passport 自動創建一個 user
對象,並且將其作爲 req.user
分配給 Request
請求對象。 稍後,我們將會用 創建 JWT 的代碼替換它。
可以使用 cURL 命令行工具進行測試:
$ # 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"}
雖然,現在這樣是可行的,但是,將策略名以字符串的形式直接傳遞給 AuthGuard()
方法會在代碼庫中引入 魔法字符串(magic strings), 所以,我們建議您創建你自己的類。如下所示:
//auth/local-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
現在,我們可以更新 /auth/login
路由 handler , 並使用 LocalAuthGuard
:
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
JWT 功能
我們已經準備好進入我們的授權系統的 JWT 部分了, 讓我們回顧並完善一下我們的需求:
- 允許用戶使用 username/password 進行身份驗證,並返回一個 JWT, 以便在對受保護的 API 端點的後續調用中使用。 爲了完成它,我們需要編寫發出 JWT 的代碼/
- 根據是否存在有效的 JWT 作爲持有者令牌,創建受保護的 API 路由。
我們需要再俺扎u那個幾個軟件包來支持我們的而 JWT 需求:
$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save-dev @types/passport-jwt
@nestjs/jwt
包依賴(更多見 here)是一個有助於 JWT 操作的使用程序包。 Passport-jwt 包是實現 JWT 策略的 Passport 包,而 @types/passport-jwt
提供了 TypeScript 類型定義。
讓我們仔細看看如何處理 POST /auth/login
請求,我們已經使用了有 passport-local 策略提供的 內置 AuthGuard 裝飾了這條線路。 這意味着:
- 路由 handler 只有在用戶已經被驗證的時候在會被調用
req
參數將會包含一個user
屬性(在passport-local 身份驗證流中由 Passport 填充)
考慮到這一點,我們現在終於可以生成一個真正的 JWT, 並在這個路由中返回它。 爲了使我的服務保持清晰的模塊化,我們將處理在 authService
中生成 JWT, 在 auth 目錄下,打開 auth.service.ts
文件,並添加 login()
方法, 然後導入 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 usersService: UsersService) {}
+ constructor(
+ private usersService: UsersService,
+ private 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()
函數來從用戶對象屬性的子集生成 JWT, 然後我們將它作爲一個簡單的對象返回, 只有一個 access_token
屬性。 注意我們選擇 sub
屬性名來保存 userId
值,以便與 JWT 標籤保持一致。 不要忘記將 JwtService 提供程序注入 AuthService。
現在我們需要更新 AuthModule
來導入新的依賴項並配置 JwtModule。
首先,在 auth 目錄下創建 Constants.ts
文件,並添加以下代碼:
// auth/constants.ts
export const jwtConstants = {
secret: 'secretKey',
};
我們將使用它在 JWT 簽名和驗證步驟之間共享密鑰。
⚠️
不要公開密鑰。 我們這裏是爲了明確代碼在做什麼。 但是在生產系統中,你必須保護好這個密鑰。 使用適當的措施,比如保密庫,環境變量或者配置服務。
現在,打開 autn/auth.module.ts
,並如下更新:
//auth/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';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, LocalStrategy],
+ exports: [AuthService],
})
export class AuthModule {}
我們使用 register()
配置 JwtModule
,並傳入一個配置對象。有關 Nest JwtModule
的詳細信息,可以參考 here , 有關可配置選項的詳細信息,可以參考here 。
現在我們可以更新 /auth/login
路由以返回一個 JWT
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
- import { AuthGuard } from '@nestjs/passport';
+ import { LocalAuthGuard } from './auth/local-auth.guard';
+ import { AuthService } from './auth/auth.service';
@Controller()
export class AppController {
constructor(private authService: AuthService) {}
- @UseGuards(AuthGuard('local'))
+ @UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
- return req.user;
+ return this.authService.login(req.user);
}
}
讓我們繼續使用 cURL 測試我們的路由:
$ # 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
實現 Passport JWT
現在我們可以解決最後一個需求, 通過要求在請求中攜帶一個有效的 JWT 來保護路由端點的訪問。 Passport 在這裏也可以幫助我們。 它提供了用於使用 JWT 令牌保護的 RESTful 的 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 中,我們遵循了前面之前描述的所有規則。 這個策略需要一些初始化,所以我們通過 在 super()
調用時傳入一個 options 對象來實現。 你可以在 here 瞭解到更多相關信息 ,在我們的案例中, 這些選項是:
jwtFromRequest
: 提供了從請求中提取JWT 的方法,在我們的 API 請求中,我們將會使用標準的方式去攜帶令牌(bearer token) ,也就是在 Authentication header中。ignoreExpiration
: 默認是 false, 它會確保 JWT 未過期,如果請求攜帶了過期的 jwt, 那麼該請求會被拒絕,並返回 401secretOrKey
: 用於jwt 簽名的密鑰字符串。
這裏的 validate()
方法值得討論, 對於 jwt-strategy, Passport 先會驗證 JWT 的簽名,並且就解析 JSON, 然後會執行我們的 validate()
方法,並傳入解碼後的 JSON 作爲其方法參數,根據 JWT 簽名的工作方式,我們保證我們會收到以前已經簽署併發給有效用戶的有效令牌。
由於這些原因,我們對validate()
回調的響應非常簡單:我們只是返回一個包含 userId
和 username
屬性的對象, 再次回想一下 Passport 將基於 validate()
方法的返回值構建一個用戶兌現個, 並將其作爲屬性附加到 Request 對象上。
還有一點值得指出的是,這種方法給我們留下了空間(可以說是“鉤子”),可以將其他業務邏輯注入到流程中。 例如,我們可以在 validate()
方法中執行數據庫的查找。已提取關於用戶的更多信息,從而在 Request 中提供更豐富的用戶對象。 這也是我們可能決定進一步令牌驗證的地方,例如在已撤銷令牌列表中查找 userid, 使我們能夠執行令牌撤銷。我們在示例代碼啊中實現的模型是一個快速的“無狀態JWT”模型,其中每個 api 調用都是基於有效JWT的存在而立即授權的,請求管道中有關請求者(其 userid 和 username )的一小部分信息。
在 AuthModule 中添加新的 JwtStrategy 作爲提供者:
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],
+ providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
通過導入在簽名 JWT 時相同的密鑰,我們確保 Passport 執行的驗證階段和 AuthService 中執行的簽名階段使用公共密鑰。
最後我們定義了繼承內置的 AuthGuard
的 JwtAuthGuard
類:
//auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
實現受保護的路由(protected route) 以及 JWT 策略守衛(JWT strategy guards)
我們現在可以實現我們的保護路由及其相關的警衛。
打開 app.controller.ts
文件,並如下更新:
- import { Controller, Request, Post, UseGuards } from '@nestjs/common';
+ import { Controller, Get, Request, Post, UseGuards } from '@nestjs/common';
+ import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { LocalAuthGuard } from './auth/local-auth.guard';
import { AuthService } from './auth/auth.service';
@Controller()
export class AppController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return this.authService.login(req.user);
}
+ @UseGuards(JwtAuthGuard)
+ @Get('profile')
+ getProfile(@Request() req) {
+ return req.user;
+ }
}
同樣,我們正在應用 AuthGuard 時, @nestjs/passport
模塊自動爲我們注入的我們配置的 passport-jwt 模塊。 這個守衛Guard 通過其默認名 “jwt” 進行關聯引用。 當我們的 GET /profile
路由被訪問的時候,這個守衛就會自動觸發我們的 passport-jwt 自定義配置的驗證邏輯, 驗證 JWT 的有效性,並且將user
屬性附加到 Request
對象上。
確保應用程序正在運行,並使用 cURL
測試路由
$ # GET /profile
$ curl http://localhost:3000/profile
$ # result -> {"statusCode":401,"message":"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 秒,這個國企時間可能太短, 處理令牌國企和刷新的細節超出了本文的範圍,然而,我們選擇它是爲了展示 JWT 的一個重要特性和 passport-jwt 策略。 如果在進行身份驗證 60 秒後才嘗試 GET /profile
請求,將會受到一個 401未授權響應。 這是因爲 Passport 會自動檢查 JWT 的過期時間,從而省去了在應用中處理的麻煩。
我們現在已經完成了 JWT 身份驗證實現。 JavaScript 客戶端(Angular/ React/ Vue)和其他應用程序現在可以驗證身份並與我們的API 服務器安全地通信。
你可以在這裏找到代碼的完整版本。 here.