身份验证是大多数现有应用程序的重要组成部分,有许多不同的方法,策略来实现用户的登录认证,nodejs 中认证用的比较多的是 passport, 该文章就来介绍一下采用 Nest 集成 passport 实现一个简单的 jwt 认证程序 (依然采用之前的 nest 项目).

# 安装 passport 及其他必要的库

h
$ yarn add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt
$ yarn add -D @types/passport-local @types/passport-jwt

# 创建认证模块与用户模块

h
$ nest g module auth
$ nest g service auth
$ nest g module users
$ nest g service users

users.service.ts
用户这里为了简单处理就采用了固定用户的策略,且密码都是明文的,在实际项目中用户一般在数据库中存储,密码采用加密存储.

用户 service 实现一个 findOne 方法,根据用户名查找用户.

t
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.tsjwt.strategy.ts 2 个文件,即 passport 的策略类

t
// 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

t
// 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 中加入一个控制器,用以测试登录

t
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
    return req.user;
}

AuthGuard 来自 @nestjs/passport .

使用 CURL 测试该接口 /api/auth/login

h
$ 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 认证策略

t
// 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 引入.

t
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

t
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

# 修改登录接口

t
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
    return this.authService.login(req.user);
    // return req.user;
}

此时我们调用登录接口时则返回给我们 access_token

h
$ 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 中添加我们的接口,用以获得用户信息

t
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Request() req) {
    return req.user;
}

现在访问 /api/auth/profile 接口需要带着之前获取到的 access_token

h
$ curl --location --request GET 'http://localhost:9000/api/profile' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjo0LCJpYXQiOjE2NjM1Njk4MDIsImV4cCI6MTY2MzU3MzQwMn0.GwFm4YpmzfJljsQPPSB3YW6Ha3hQ3TipPJbkVExPF30'
{"userId":4,"username":"admin"}

至此,简单的 jwt 登录与认证已经实现了.

请我喝杯[咖啡]~( ̄▽ ̄)~*

一个放羊娃 微信支付

微信支付

一个放羊娃 支付宝

支付宝