身份验证是大多数现有应用程序的重要组成部分,有许多不同的方法,策略来实现用户的登录认证,nodejs 中认证用的比较多的是 passport, 该文章就来介绍一下采用 Nest 集成 passport 实现一个简单的 jwt 认证程序 (依然采用之前的 nest 项目).
# 安装 passport 及其他必要的库
| $ yarn add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt | |
| $ yarn add -D @types/passport-local @types/passport-jwt | 
# 创建认证模块与用户模块
| $ nest g module auth | |
| $ nest g service auth | |
| $ nest g module users | |
| $ nest g service users | 
users.service.ts
用户这里为了简单处理就采用了固定用户的策略,且密码都是明文的,在实际项目中用户一般在数据库中存储,密码采用加密存储.
用户 service 实现一个 findOne 方法,根据用户名查找用户.
| import { Injectable } from '@nestjs/common'; | |
| export type User = { | |
| userId:number; | |
| username:string; | |
| password:string; | |
| }; | |
| @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', | |
| }, | |
|             { | |
| userId: 4, | |
| username: 'admin', | |
| password: '111111', | |
| }, | |
| ]; | |
|     } | |
| async findOne(username: string): Promise<User | undefined> { | |
| return this.users.find(user => user.username === username); | |
|     } | |
| } | 
# 编写 passport 策略
在 auth 模块下我们创建 local.strategy.ts 和 jwt.strategy.ts 2 个文件,即 passport 的策略类
| // src/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; | |
|     } | |
| } | 
passport-local 策略类需要实现一个 validate 接口,该接口接收 2 个参数 (username 和 password), 返回的值则会被加载到 Request 对象上
以下是 auth.service.ts
| // 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; | |
|     } | |
| } | 
通过 username 和 password 来找到相应的用户,将用户的信息返回
我们这下就可以测试登录了
# 测试登录
现在在 app.controller.ts 中加入一个控制器,用以测试登录
| @UseGuards(AuthGuard('local')) | |
| @Post('auth/login') | |
| async login(@Request() req) { | |
| return req.user; | |
| } | 
AuthGuard 来自 @nestjs/passport .
使用 CURL 测试该接口 /api/auth/login
| $ curl --location --request POST 'http://localhost:9000/api/auth/login' \ | |
| --header 'Content-Type: application/json' \ | |
| --data-raw '{"username":"admin","password":"111111"}' | |
| {"userId":4,"username":"admin"} | 
通过用户名与密码成功获取到了用户的其他信息.
# 实现 JWT 登录与认证
至此我们的工作只完成了一半,只实现了一个简单的登录,还没有认证.
在以前,登录之后需要将用户的信息存储到 session,cookie 或数据库中,返回用户一个令牌,用户之后访问则携带令牌进行认证,现在 JWT 策略已逐渐替代 session,cookie 变为更主流的认证方式.
接下来我们实现一个 JWT 的登录与认证服务.
# 添加 jwt 认证策略
首先添加 passport 的 jwt 认证策略
| // src/auth/jwt.strategy.ts | |
| import { ExtractJwt, Strategy } from 'passport-jwt'; | |
| import { PassportStrategy } from '@nestjs/passport'; | |
| import { Injectable } from '@nestjs/common'; | |
| @Injectable() | |
| export class JwtStrategy extends PassportStrategy(Strategy) { | |
| constructor() { | |
| super({ | |
| jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),// 使用 header 中 bearer token 的方式 | |
| ignoreExpiration: false, | |
| secretOrKey: "secretKey",// 这里的 key 需要小新保管,通常采用配置文件中引入 | |
| }); | |
|     } | |
| async validate(payload: any) { | |
| return { userId: payload.sub, username: payload.username }; | |
|     } | |
| } | 
同样的, validate 方法的返回值会加载进 Request 对象上.
auth.module.ts 中需要引入 passport 并将 passport 策略作为 providers 引入.
| 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'; | |
| import { JwtModule } from '@nestjs/jwt'; | |
| import { jwtConstants } from './constants'; | |
| import { JwtStrategy } from './jwt.strategy'; | |
| @Module({ | |
| imports: [ | |
|     UsersModule, | |
| PassportModule.register({ defaultStrategy: 'jwt' }), | |
| JwtModule.register({ | |
| secret: jwtConstants.secret, | |
| signOptions: { expiresIn: '3600s' }, | |
| }), | |
| ], | |
| providers: [AuthService, LocalStrategy, JwtStrategy], | |
| exports: [AuthService], | |
| }) | |
| export class AuthModule {} | 
# 添加 login 方法
修改 auth.service.ts 添加 login 方法,使用 jwt 生成 access_token
| constructor( | |
| private readonly usersService: UsersService, | |
| private readonly jwtService: JwtService | |
| ) {} | |
| async login(user: any) { | |
| const payload = { username: user.username, sub: user.userId }; | |
| return { | |
| access_token: this.jwtService.sign(payload), | |
| }; | |
| } | 
JwtService 来自 @nestjs/jwt
# 修改登录接口
| @UseGuards(AuthGuard('local')) | |
| @Post('auth/login') | |
| async login(@Request() req) { | |
| return this.authService.login(req.user); | |
|     // return req.user; | |
| } | 
此时我们调用登录接口时则返回给我们 access_token
| $ curl --location --request POST 'http://localhost:9000/api/auth/login' \ | |
| --header 'Content-Type: application/json' \ | |
| --data-raw '{"username":"admin","password":"111111"}' | |
| {"access_token":"xxxxxxxxxxx"} | 
# 实现 jwt 认证
此时我们就可以采用 jwt 来进行认证了,在 app.controller.ts 中添加我们的接口,用以获得用户信息
| @UseGuards(AuthGuard('jwt')) | |
| @Get('profile') | |
| getProfile(@Request() req) { | |
| return req.user; | |
| } | 
现在访问 /api/auth/profile 接口需要带着之前获取到的 access_token
| $ curl --location --request GET 'http://localhost:9000/api/profile' \ | |
| --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjo0LCJpYXQiOjE2NjM1Njk4MDIsImV4cCI6MTY2MzU3MzQwMn0.GwFm4YpmzfJljsQPPSB3YW6Ha3hQ3TipPJbkVExPF30' | |
| {"userId":4,"username":"admin"} | 
至此,简单的 jwt 登录与认证已经实现了.
