跳到主要内容

鉴权

初始化

nest new nest-project
nest g mo auth
nest g s auth
nest g co test
nest g mo test
nest g res user

使用数据源

"mysql": "^2.18.1",
"typeorm": "^0.3.11"

https://typeorm.io/data-source

src/app-data-source.ts
import { DataSource as _DataSource } from "typeorm"
import { User } from "src/user/entities/user.entity"
export const dataSource = new _DataSource({
type: 'mysql', //数据库类型
username: 'dbuser', //账号
password: 'dbpwd', //密码
host: 'localhost', //host
port: 3306, //端口
database: 'nestdb', //库名
synchronize:true,// ! 不要在生产中使用
entities: [User], // typeorm loads entities from this directory
})

dataSource.initialize()
.then(() => {
console.log("Data Source has been initialized!")
})
.catch((err) => {
console.error("Error during Data Source initialization", err)
})

创建用户

为啥这里叫创建用户,因为这里目前仅是测试普通的增加用户,后续真正注册时还要加入验证码,或者vx登录的情况

"bcryptjs": "^2.4.3",
"class-validator": "^0.13.2",

更新路由

user.controller.ts
@Post('create')
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}

定义dto

create-user.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
username:string
@IsNotEmpty()
password:string
}

定义实体

user.entity.ts
import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn,BeforeUpdate } from 'typeorm';
import { hash } from 'bcryptjs'
@Entity('user')
export class User {
@PrimaryGeneratedColumn('uuid')
id: number;

@Column({ length: 100 })
username: string; // 用户名

@Column({ length: 100 })
nickname: string; //昵称

@Column()
password: string; // 密码

@Column()
avatar: string; //头像

@Column()
email: string;

@Column('simple-enum', { enum: ['common', 'root'] })
role: string; // 用户角色

@Column({
name: 'create_time',
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;

@Column({
name: 'update_time',
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;

@BeforeInsert()
async encryptPwd() {
this.role = 'common';//防止注册root
this.password = await hash(this.password,10);
}
}

定义service

user.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { dataSource } from 'src/app-data-source';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
async create(createUserDto: CreateUserDto) {
const { username } = createUserDto;
const userRepository = dataSource.getRepository(User)
const existUser = await userRepository.findOneBy({ username })
if(existUser){
throw new HttpException("用户名已存在", HttpStatus.BAD_REQUEST)
}
// https://blog.csdn.net/qq_42501092?type=blog&year=2022&month=11
// 这个链接表明为啥要合并对象,而不是直接扔给values
const entiry = Object.assign(new User(), createUserDto)
const data = await userRepository
.createQueryBuilder()
.insert()
.values(entiry)
.execute()
return data.identifiers;
}
}

环境变量

"@nestjs/config": "^2.2.0",
app.controller.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

使用环境变量

@Get()
getHello(): string {
return process.env.SECRET;
}

登录校验

循环依赖

auth模块和user模块互相依赖,使用对方的service,需要使用forwardRef来解决

auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from 'src/user/user.module';
@Module({
imports:[UserModule],
providers: [AuthService],
exports:[AuthService]
})
export class AuthModule {}
user.module.ts
import { forwardRef, Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { AuthModule } from 'src/auth/auth.module';
@Module({
imports:[forwardRef(() => AuthModule)],
controllers: [UserController],
providers: [UserService],
exports:[UserService]
})
export class UserModule {}

在user中添加login路由,调用auth的service进行校验

user.controller.ts
import { AuthService } from 'src/auth/auth.service';
@Controller('user')
export class UserController {
constructor(
private readonly userService: UserService,
private readonly authService: AuthService,
) {}

@Post('login')
login(@Body() body){
const {username,password} = body
return this.authService.login(username,password)
}
}

使用compareSync对比密码是否正确

auth.service.ts
import { Injectable } from '@nestjs/common';
import { compareSync } from 'bcryptjs';
import { UserService } from 'src/user/user.service';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.userService.findByName(username);
if (user && compareSync(password,user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(username:string,password:string){
const user = await this.validateUser(username,password)
if(user){
return '校验成功'
}else{
return '校验失败'
}
}
}

在auth的service中使用了user的service的findByName方法

user.service.ts
async findByName(username:string) {
const userRepository = dataSource.getRepository(User)
return await userRepository.findOneBy({ username })
}

Jwt

"@nestjs/jwt": "^9.0.0",
"@nestjs/passport": "^9.0.0",
"passport-jwt": "^4.0.0",

在auth中使用JwtModule,并注入JwtStrategy

auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from 'src/user/user.module';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UserModule,
JwtModule.register({
secret: process.env.SECRET,
signOptions: { expiresIn: '2 days' },
})
],
providers: [AuthService,JwtStrategy],
exports:[AuthService]
})
export class AuthModule {}

在登录成功后生成token

auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { compareSync } from 'bcryptjs';
import { UserService } from 'src/user/user.service';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService
) { }
async validateUser(username: string, password: string): Promise<any> {
const user = await this.userService.findByName(username);
if (user && compareSync(password, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(username: string, password: string) {
const user = await this.validateUser(username, password)
if (user) {
return {
"token": this.jwtService.sign(user, {
secret: process.env.SECRET
})
}
} else {
throw new UnauthorizedException();
}
}
}

jwt策略

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(),
ignoreExpiration: false,
secretOrKey: process.env.SECRET,
});
}

async validate(payload: any) {
// 用于二次校验等
if(payload.id){
return true
}
return false;
}
}

测试登录

test.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@UseGuards(AuthGuard('jwt'))
@Controller('test')
export class TestController {
@Get()
findAll(){
return 'test findAll'
}
}

【小测一下】

注册:

image-20221204225234245

登录:

image-20221204225323314

鉴权失败:

image-20221204225426651

鉴权成功:

image-20221204225503311