这是我参与「第五届青训营 」伴学笔记创作活动的第 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 来标识用户,一定程度上提升了安全性。