1. 安装jwt依赖
pnpm add @nestjs/jwt @nestjs/passport @nestjs/swagger @types/passport-jwt crypto express passport passport-jwt swagger-ui-express uuid
2. 创建认证文件
nest g mo auth --no-spec
nest g s auth --no-spec
nest g co auth --no-spec
nest g mo user --no-spec
nset g s user --no-spec
nest g co user --no-spec
3. 创建jwt文件
4. 编写文件
4.1 编写jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from '../../config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { message: '123' };
}
}
4.2 编写index.ts
export * from './jwt.strategy';
4.3 编写main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('CRUD')
.setDescription('一个练手标准项目')
.setVersion('1.0')
.addTag('future')
.addBasicAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
'jwt',
)
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(6666);
}
bootstrap();
4.4 编写utils/cryptogram.ts
import * as crypto from 'crypto';
/**
* Make salt
*/
export function makeSalt(): string {
return crypto.randomBytes(3).toString('base64');
}
/**
* 将密码hash
* @param password 用户输入的密码
* @param salt 盐
* @returns
*/
export function encryptPassword(password: string, salt: string): string {
if (!password || !salt) {
return '';
}
const tempSalt = Buffer.from(salt, 'base64');
return (
// 10000 代表迭代次数 16代表长度
crypto.pbkdf2Sync(password, tempSalt, 10000, 16, 'sha1').toString('base64')
);
}
4.5 编写app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './db/database.module';
import { PhotoModule } from './photo/photo.module';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
@Module({
imports: [DatabaseModule, PhotoModule, AuthModule, UserModule],
controllers: [],
providers: [],
})
export class AppModule {}
4.6 编写user.service.ts
import { Repository } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { User } from 'src/entity/user/user.entity';
import { UsersDTO } from 'src/dto/userdto';
import { v4 as uuidv4 } from 'uuid';
import { CODE } from 'src/code/code';
import { encryptPassword, makeSalt } from 'src/utils/cryptogram';
import { registerDTO } from 'src/dto/authdto';
@Injectable()
export class UserService {
constructor(
@Inject('USER_REPOSITORY')
private userRepository: Repository<User>,
) {}
/**
* 通过邮箱:查找用户
* @param email 用户邮箱
* @returns
*/
async findByEmail(email: string): Promise<UsersDTO> {
return await this.userRepository.findOne({
where: {
email,
},
});
}
/**
* 通过用户唯一id查找用户
* @param uuid 用户唯一id
* @returns
*/
async findOne(uuid: string): Promise<UsersDTO> {
return await this.userRepository.findOne({
where: {
uuid: uuid,
},
});
}
/**
* 根据用户email 和 data来更新
* @param email 用户email地址
* @param data 要变更的信息
* @returns
*/
async updateUser(email: string, data: Partial<UsersDTO>) {
await this.userRepository.update({ email }, data);
return await this.userRepository.findOne({
where: {
email,
},
});
}
/**
* 根据用户邮箱删除用户
* @param email 用户邮箱
* @returns
*/
async deleteUser(email: string) {
await this.userRepository.delete({ email });
return {
code: CODE.HTTP_OK,
message: '删除成功',
};
}
/**
* 清空数据库中user表中所有数据
*/
async clearUser(isClearAll: boolean) {
if (isClearAll) {
await this.userRepository.clear();
return {
code: CODE.HTTP_OK,
message: '清空user表成功',
};
}
}
/**
* 注册用户
* @param body 注册用户体
* @returns
*/
async authRegister(body: registerDTO) {
const { email, password } = body;
const userExist = await this.findByEmail(email);
console.log(userExist);
if (userExist) {
return {
HttpStatus: CODE.HTTP_CREATED,
message: '用户已经存在',
};
}
const uuid = uuidv4();
// 加盐加密
const salt = makeSalt();
const hashPwd = encryptPassword(password, salt);
Object.keys(body).forEach((item) => {
if (item === 'password') body[item] = hashPwd;
});
const reqBody = Object.assign({ uuid, salt }, body);
return this.userRepository.save(reqBody);
}
}
4.7 编写user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { DatabaseModule } from '../db/database.module';
import { userProviders } from '../entity/user/user.providers';
@Module({
imports: [DatabaseModule],
providers: [UserService, ...userProviders],
controllers: [UserController],
})
export class UserModule {}
4.8 编写user.controller.ts
import {
Body,
Controller,
Delete,
Get,
HttpStatus,
Param,
Patch,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { registerDTO } from 'src/dto/authdto';
import { UserFindByEmail, UserFindOne, UsersDTO } from 'src/dto/userdto';
import { UserService } from './user.service';
@Controller('user')
@ApiTags('用户模块')
export class UserController {
constructor(private readonly userService: UserService) {}
/**
* 查找用户
* @param email 用户邮箱
* @returns
*/
@Get('findByEmail')
@ApiOperation({
summary: '根据邮箱查询某个用户',
})
@ApiQuery({ name: 'email', description: '用户email', type: UserFindByEmail })
async findByEmail(@Query('email') email: string) {
return await this.userService.findByEmail(email);
}
/**
* 通过用户唯一id查找用户
* @param uuid 用户唯一id
* @returns
*/
@Get()
@ApiOperation({
summary: '根据用户uuid查找用户',
})
@ApiQuery({ name: 'uuid', description: '用户uuid', type: UserFindOne })
async findOne(@Query('uuid') uuid: string) {
return await this.userService.findOne(uuid);
}
@Put('/update/:email')
@ApiOperation({
summary: '根据用户email更新用户信息',
})
async updateUser(@Param('email') email: string, @Body() data: UsersDTO) {
const checkUser = await this.findByEmail(email);
if (!checkUser) {
return {
HttpStatus: 201,
message: '用户不存在',
};
}
await this.userService.updateUser(email, data);
return {
status: HttpStatus.OK,
message: '用户信息更新成功',
};
}
/**
* 根据用户邮箱删除用户
* @param email 用户邮箱
* @returns
*/
@Delete('/delete/:email')
@ApiOperation({
summary: '根据用户email删除用户',
})
async deleteOne(@Param('email') email: string) {
const checkUser = await this.findByEmail(email);
if (!checkUser) {
return {
HttpStatus: 201,
message: '用户不存在',
};
}
await this.userService.deleteUser(email);
return {
status: HttpStatus.OK,
message: '用户删除成功',
};
}
/**
* 清空数据库中user表中所有数据
*/
@Get('clearUser')
@ApiOperation({
summary: '清空用户表中所有用户',
})
async clearUser(@Query('isClear') isClear: boolean) {
return await this.userService.clearUser(isClear);
}
/**
* 注册用户
* @param body 注册用户体
* @returns
*/
@Post('register')
@ApiOperation({
summary: '注册用户',
})
async authRegister(@Body() body: registerDTO) {
return await this.userService.authRegister(body);
}
}
4.9 编写user.entity.ts
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('user')
export class User {
@PrimaryGeneratedColumn({
type: 'int',
name: 'id',
comment: '主键id',
})
id: number;
@Column('varchar', {
nullable: false,
length: 150,
name: 'uuid',
comment: 'uuid',
})
uuid: string;
@Column('varchar', {
nullable: false,
name: 'salt',
comment: '密码盐',
})
salt: string;
@Column('varchar', {
nullable: false,
name: 'username',
comment: '用户名',
})
username: string;
@Column('varchar', {
nullable: false,
name: 'password',
comment: '密码',
})
password: string;
@Column('varchar', {
name: 'email',
length: 100,
comment: '邮箱',
})
email: string;
@Column('varchar', {
nullable: false,
name: 'avatar',
comment: '头像',
})
avatar: string;
@Column('int', {
nullable: false,
name: 'active',
comment: '是否激活状态',
})
active: 0; // 0 未激活 1 激活
@CreateDateColumn({
type: 'datetime',
comment: '创建时间',
name: 'create_at',
})
createAt: Date;
@UpdateDateColumn({
type: 'datetime',
comment: '更新时间',
name: 'update_at',
})
updateAt: Date;
}
4.10 编写user.providers.ts
import { DataSource } from 'typeorm';
import { User } from './user.entity';
export const userProviders = [
{
provide: 'USER_REPOSITORY',
useFactory: (dataSource: DataSource) => dataSource.getRepository(User),
inject: ['DATA_SOURCE'],
},
];
4.11 编写userDto.ts
import { ApiPropertyOptional } from '@nestjs/swagger';
export class UsersDTO {
uuid: string;
@ApiPropertyOptional({
description: '用户名',
})
username: string;
salt: string;
@ApiPropertyOptional({
description: '密码',
})
password: string;
@ApiPropertyOptional({
description: '邮箱',
maxLength: 100,
})
email: string;
@ApiPropertyOptional({
description: '头像',
})
avatar: string;
@ApiPropertyOptional({
description: '0 未激活 1 激活',
})
active: 0; // 0 未激活 1 激活
createAt: Date;
updateAt: Date;
}
export class UserFindByEmail {
@ApiPropertyOptional({
description: '邮箱',
})
email: string;
}
export class UserFindOne {
@ApiPropertyOptional({
description: 'uuid',
})
uuid: string;
}
4.12 编写config/constants
export const jwtConstants = {
secret: 'super_hero', // 秘钥
};
4.13 编写config/index.ts
export * from './constants';
4.14 编写code/code.ts
export enum CODE {
HTTP_OK = '200',
HTTP_CREATED = '201',
REP_WARNING = '1001',
REP_ERROR = '1002',
}
4.15 编写auth.service.ts
import { encryptPassword } from 'src/utils/cryptogram';
import { authDto, loginData, registerDTO } from 'src/dto/authdto';
import { UserService } from './../user/user.service';
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { CODE } from 'src/code/code';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UserService,
private readonly jwtService: JwtService,
) {}
// 校验用户信息
async validateUser({ email, password }: loginData) {
const user = await this.usersService.findByEmail(email);
if (user) {
const { password: PWD, salt } = user;
const hashPassword = encryptPassword(password, salt);
if (hashPassword === PWD) {
return {
code: CODE.HTTP_OK,
user,
};
} else {
return {
code: CODE.HTTP_OK,
message: '用户名或者密码错误',
};
}
} else {
return {
code: CODE.REP_WARNING,
message: '用户不存在',
};
}
}
async certificate(user: authDto) {
const { email, password } = user;
const payload = Object.assign({ email, password });
try {
const token = this.jwtService.sign(payload);
return {
code: CODE.HTTP_OK,
data: {
token,
},
};
} catch (error) {
return {
code: CODE.REP_WARNING,
msg: '账号或密码错误',
};
}
}
async register(body: registerDTO) {
return await this.usersService.authRegister(body);
}
}
4.16 编写auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserService } from '../user/user.service';
import { userProviders } from 'src/entity/user/user.providers';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from 'src/config';
import { JwtStrategy } from './strategy';
import { DatabaseModule } from 'src/db/database.module';
@Module({
imports: [
DatabaseModule,
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '5min' },
}),
],
controllers: [AuthController],
providers: [AuthService, UserService, ...userProviders, JwtStrategy],
})
export class AuthModule {}
4.17 编写auth.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { CODE } from 'src/code/code';
import { AuthGuard } from '@nestjs/passport';
import { authDto, loginData } from 'src/dto/authdto';
@Controller('auth')
@ApiTags('用户认证模块')
export class AuthController {
constructor(private readonly authService: AuthService) {}
/**
* 用户登录
* @param body 登录信息
*/
@Post('login')
@ApiOperation({
summary: '用户登录',
})
async login(@Body() body: loginData) {
const { email, password } = body;
console.log('JWT验证 - Step 1: 用户请求登录');
const authResult = await this.authService.validateUser({ email, password });
switch (authResult.code) {
case CODE.HTTP_OK:
return this.authService.certificate(authResult.user);
case CODE.REP_WARNING:
return {
code: 600,
msg: `账号或密码不正确`,
};
default:
return {
code: 600,
msg: `查无此人`,
};
}
}
@Post('register')
@ApiOperation({
summary: '注册',
})
@ApiBearerAuth('jwt')
@UseGuards(AuthGuard('jwt')) // 使用 'JWT' 进行验证
async register(@Body() body: authDto) {
return await this.authService.register(body);
}
}
5. 测试
5.1 测试注册用户
此时由于加了auth.controller.ts中加了jwt守卫
@UseGuards(AuthGuard('jwt')) // 使用 'JWT' 进行验证
此时我们需要先登录才可以注册,将登录的token放到postman的中
5.2 测试登录用户
将token放入认证,此时可以注册