NestJS 用户模块开发(三)| 青训营笔记

305 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天。我们 Bocchi 小组采用 NestJS 开发这次掘金站点的后端,这几天我们尝试完成了用户和鉴权模块的开发,这篇文章就来简要记录下实现过程(具体代码看下方仓库)。

仓库地址: github.com/Bocchi-Deve…

安装

在项目中我们约定模块都放在 /modules 文件下,同时用户开发需要依赖 auth 和 user 模块,因此执行如下命令:

nest g resource modules/user
nest g resource modules/auth

因为后台涉及用户登录,所以我们需要使用 token 鉴权,因此需要安装对应依赖:

pnpm i passport passport-jwt @nestjs/passport @nestjs/jwt

实现

我们需要在 auth 文件夹中编写 jwt 策略:

// modules/auth/jwt.strategy.ts
import { ExtractJwt, Strategy, StrategyOptions } from 'passport-jwt'

import { Injectable, UnauthorizedException } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'

import { __secret } from './auth.module'
import { AuthService } from './auth.service'
import { JwtPayload } from './interfaces/jwt-payload.interface'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: __secret, 
      ignoreExpiration: false,
    } as StrategyOptions)
  }

  async validate(payload: JwtPayload) {
    const user = await this.authService.verifyPayload(payload)

    if (user) {
      return user
    }
    throw new UnauthorizedException('身份已过期')
  }
}

这里的 __secret 采用优先采用 config 中配置 jwtSecret,如未配置就用 node-machine-id 来生成。

import { machineIdSync } from 'node-machine-id'

const getMachineId = () => {
  const id = machineIdSync()
  return id
}
export const __secret: any =
  SECURITY.jwtSecret ||
  Buffer.from(getMachineId()).toString('base64').slice(0, 15) ||
  'asjhczxiucipoiopiqm2376'

const jwtModule = JwtModule.registerAsync({
  useFactory() {
    return {
      secret: __secret,
      signOptions: {
        expiresIn: SECURITY.jwtExpire,
        algorithm: 'HS256',
      },
    }
  },
})

依赖的 service:

import { Model } from 'mongoose'

import { Injectable } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { InjectModel } from '@nestjs/mongoose'

import { MasterLostException } from '~/common/exceptions/master-lost.exception'

import { UserModel } from '../user/user.model'
import { JwtPayload } from './interfaces/jwt-payload.interface'

@Injectable()
export class AuthService {
  constructor(
    @InjectModel(UserModel.name)
    private readonly userModel: Model<UserModel>,
    private readonly jwtService: JwtService,
  ) {}

  async signToken(id: string) {
    const user = await this.userModel.findById(
      { _id: id.toString() },
      'authCode',
    )
    if (!user) {
      throw new MasterLostException()
    }
    const authCode = user.authCode
    const payload = {
      authCode,
    }

    return this.jwtService.sign(payload)
  }

  async verifyPayload(payload: JwtPayload) {
    const user = await this.userModel
      .findOne({
        authCode: payload.authCode,
      })
      .select(['+authCode'])
    if (!user) {
      throw new MasterLostException()
    }
    return user && user.authCode === payload.authCode ? user : null
  }
}

这里我们采用了一个 authCode 来标识用户,一定程度上提升了安全性。