鉴权
初始化
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'
}
}
【小测一下】
注册:
登录:
鉴权失败:
鉴权成功: