废话部分:
宝们,最近再搞一个秘密项目。
后端我打算是用nestjs来做的,刚好我也是变学边做,所以就整理了下这篇文章来记录下怕忘了。

废话部分

首先,咱们去创建一个nestjs项目,使用nest new xxx,我这里简单取名为auth,包管理工具我这里就用npm了,大家可以根据自己电脑上安装的包管理工具去选择。至于安装速度?dddd咳咳,无非就是换源爬墙嘛,大家都会,就不多说了。

安装完成后,使用nest g res auth去生成restful风格的auth模块。

然后我们找到auth.controllert.tsauth.service.ts文件,把这俩里的方法都先删除,我们自己写。

由于咱们要去注册用户,肯定是需要一张用户的表,接着咱们先去连接数据库,我这里使用mysql数据库,Nest提供了与现成的TypeORM@nestjs/typeorm的紧密集成,我这里也是使用TypeORM

数据库部分

npm install --save @nestjs/typeorm typeorm mysql2

然后在app.module.ts文件去配置连接mysql

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [AuthModule, TypeOrmModule.forRoot({
    type: 'mysql', // 数据库类型
    host: 'localhost', // 主机名
    port: 3306, // 端口
    username: 'root', // 用户名
    password: '123456', // 密码
    database: 'nestjs', // 数据库名称
    synchronize: true,
    retryDelay: 500, //重试连接数据库间隔
    retryAttempts: 10,//重试连接数据库的次数
    autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
  }),],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule { }

接着在auth.entity.ts文件中去写关于用户表的配置,我这里就做三个字段id,username,password

import { Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class NV_Users {

    // id为主键并且自动递增
    @PrimaryGeneratedColumn()
    id:number

    @Column()
    username:string

    @Column()
    password:string
}

然后使用TypeOrmModule.forFeature()方法注册该表,这样我们可以使用@InjectRepository()装饰器将NV_UsersRepository注入到auth.service.ts中。

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';


@Module({
  imports: [TypeOrmModule.forFeature([NV_Users])],
  controllers: [AuthController],
  providers: [AuthService]
})
export class AuthModule { }

注入NV_UsersRepositoryauth.service.ts中。

import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { UpdateAuthDto } from './dto/update-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';


@Injectable()
export class AuthService {
    constructor(@InjectRepository(NV_Users) private readonly user:Repository<NV_Users>){}
}

登录注册部分

接着在auth.controller.ts去写对应的方法,去调用auth.service.ts中对应的方法。 下面是auth.controller.ts文件代码:

import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateAuthDto } from './dto/create-auth.dto';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) { }


  // 注册
  @Post("/signup")
  signup(@Body() signupData: CreateAuthDto) {
    return this.authService.signup(signupData)
  }

  // 登录
  @Post("/login")
  login(@Body() loginData: CreateAuthDto) {
    return this.authService.login(signupData)
  }
}

写了这么多,咱们先跑起来npm run start:dev看看nv_users这张表有没有创建,正常跑起来后会自动创建。

然后我们先简单写下auth.service.ts中的代码

import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';


@Injectable()
export class AuthService {
    constructor(@InjectRepository(NV_Users) private readonly user: Repository<NV_Users>) { }

    // 注册
    signup(signupData: CreateAuthDto) {
        console.log(signupData);
        return "注册成功"
    }

    // 登录
    login(loginData: CreateAuthDto) {
        console.log(loginData);
        return "登录成功"
    }
}

着就是来测试下接口有没有问题,这里我使用apipost这个软件,当然其他接口测试软件也是可以的,比如postman等等。nestjs默认端口是3000,可以在main.ts去修改,我这里就使用默认的了,然后接口路径就是auth.controller.ts中拼接起来,比如我这里注册接口就是/auth/signup,其他同理。

接口测试没问题,接着就是写业务代码了,写业务代码前需要安装几个包:

npm i bcryptjs // 这个是对用户密码进行加密的
npm i @nestjs/jwt // 用于生成token

其中也是用到了几个方法:
bcryptjs.hashSync()方法是对用户密码进行加密用的,hashSync()方法第一个参数为用户密码,第二个为密码盐(简单理解为加密的程度)。
bcryptjs.compareSync()方法是对用户的密码和加密后的密码进行比较的,如果正确返回true,错误则返回false,第一个参数为登录时用户输入的密码,第二个为查出来的加密后的密码。这里提一下,加密密码的方法是不可逆的,也就是没有解密的功能,这样做也是保证用户的安全。
JwtService.sign()方法即生成token的方法,参数可传入我们想传入给前端的用户对象,这样前端可以把token拿去解密,拿到用户对象,切记不能将密码传入。

我们先新建一个关于token相关的配置文件constants.ts

export const jwtConstants = {
    secret: "leeKey", // 密钥
    expiresIn: "60s" // token有效时间  
}

接着在auth.module.ts中配置

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from "./constants"


@Module({
  imports: [TypeOrmModule.forFeature([NV_Users]), JwtModule.register({
    secret: jwtConstants.secret,
    signOptions: { expiresIn: jwtConstants.expiresIn }
  })],
  controllers: [AuthController],
  providers: [AuthService]
})
export class AuthModule { }

登录注册我这里就一次性写完了,代码看下面👇

import { BadRequestException, Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcryptjs from "bcryptjs"
import { JwtService } from "@nestjs/jwt"


@Injectable()
export class AuthService {
    constructor(
        @InjectRepository(NV_Users) private readonly user: Repository<NV_Users>,
        private readonly JwtService: JwtService
    ) { }

    // 注册
    async signup(signupData: CreateAuthDto) {

        const findUser = await this.user.findOne({
            where: { username: signupData.username }
        })
        if (findUser && findUser.username === signupData.username) return "用户已存在"
        // 对密码进行加密处理
        signupData.password = bcryptjs.hashSync(signupData.password, 10)
        await this.user.save(signupData)
        return "注册成功"
    }

    // 登录
    async login(loginData: CreateAuthDto) {

        const findUser = await this.user.findOne({
            where: { username: loginData.username }
        })
        // 没有找到
        if (!findUser) return new BadRequestException("用户不存在")

        // 找到了对比密码
        const compareRes: boolean = bcryptjs.compareSync(loginData.password, findUser.password)
        // 密码不正确
        if (!compareRes) return new BadRequestException("密码不正确")
        const payload = { username: findUser.username }

        return {
            access_token: this.JwtService.sign(payload),
            msg: "登录成功"
        }
    }
}

这时候我们可以再次使用apipost等软件测试下接口,看看返回值是否正确。

身份验证部分

下面我们来看看身份验证这一块的功能,如何完成。 首先我们新建文件/common/public.decorator.ts,创建自定义装饰器

import { SetMetadata } from "@nestjs/common";

export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

接着我们安装这几个包

npm install --save @nestjs/passport passport-jwt
npm install @types/passport-jwt --save-dev

新建jwt-auth.grard.ts文件,用于全局守卫,将未携带token的接口进行拦截,代码👇

import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport"
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs"
import { IS_PUBLIC_KEY } from "src/common/public.decorator";


@Injectable()

export class jwtAuth extends AuthGuard("jwt") {
    constructor(private reflector: Reflector) {
        super()
    }

    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
            context.getHandler(),
            context.getClass()
        ])
        console.log(isPublic, "isPublic");
        if (isPublic) return true
        return super.canActivate(context)
    }
}

新建jwt-auth.strategy.ts,该文件为验证策略,也就是验证前端请求头中携带的token,代码👇

import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { jwtConstants } from "./constants";


export interface JwtPayload {
    username: string
}


@Injectable()
// 验证请求头中的token
export default class JwtAuthStrategy extends PassportStrategy(Strategy, "jwt") {
    constructor() {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: jwtConstants.secret
        })
    }

    async validate(payload: JwtPayload) {
        console.log(payload.username);
        const { username } = payload
        return {
            username
        }
    }
}

接着在auth.module.ts的providers中配置JwtAuthStrategy,代码👇

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from "./constants"
import { JwtAuthStrategy } from "./jwt-auth.strategy"


@Module({
  imports: [TypeOrmModule.forFeature([NV_Users]), JwtModule.register({
    secret: jwtConstants.secret,
    signOptions: { expiresIn: jwtConstants.expiresIn }
  })],
  controllers: [AuthController],
  providers: [AuthService, JwtAuthStrategy]
})
export class AuthModule { }

最后在app.module.ts将其注册为全局守卫

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './auth/jwt-auth.grard';

@Module({
  imports: [AuthModule, TypeOrmModule.forRoot({
    type: 'mysql', // 数据库类型
    host: 'localhost', // 主机名
    port: 3306, // 端口
    username: 'root', // 用户名
    password: '123456', // 密码
    database: 'nestjs', // 数据库名称
    synchronize: true,
    retryDelay: 500, //重试连接数据库间隔
    retryAttempts: 10,//重试连接数据库的次数
    autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
  }),],
  controllers: [AppController],
  // 注册为全局守卫
  providers: [AppService, {
    provide: APP_GUARD,
    useClass: JwtAuthGuard
  }],
})
export class AppModule { }

以上配置完成后,再次请求注册接口,返回401,表示我们没有权限,我们需要给通用接口(注册和登录接口)都加上@Public装饰器就可以请求了。

以上就是关于全局身份验证的内容了,这样做之后,我们只需要给你通用接口加上@Pulic()装饰器即可,随后我们需要验证的接口就不用一个一个去加守卫了。

参考文章:nestjs使用jwt完成注册、登录、全局身份验证