第五阶段:用户模块 + JWT 登录认证

完成后你会得到:


POST   /auth/register
POST   /auth/login

GET    /books
Authorization: Bearer xxxxx

没有 Token:


{
  "statusCode": 401,
  "message": "Unauthorized"
}

有 Token:


[
  {
    "id": 1,
    "name": "NestJS实战"
  }
]

先理解认证流程


用户登录

 ↓

账号密码

 ↓

验证成功

 ↓

生成 JWT

 ↓

返回给前端

 ↓

前端存储 Token

 ↓

请求时携带 Token

 ↓

Guard验证

 ↓

允许访问

第一部分:创建 User 模块

生成资源:


nest g resource users

选择:


REST API

然后:


Yes

生成:


src
├─ users
│  ├─ dto
│  ├─ entities
│  ├─ users.controller.ts
│  ├─ users.service.ts
│  └─ users.module.ts

第二部分:创建 User 实体

修改:


src/users/entities/user.entity.ts

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    unique: true,
  })
  username: string;

  @Column()
  password: string;
}

unique


@Column({
  unique: true,
})

数据库:


username varchar(255) unique

防止重复注册。


第三部分:注册 User Entity

修改:


users.module.ts

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

第四部分:注入 Repository

修改:


users.service.ts

import { Injectable } from '@nestjs/common';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}
  async create(username: string, password: string) {
    const user = this.userRepository.create({
      username,
      password,
    });

    return this.userRepository.save(user);
  }

  findAll() {
    return `This action returns all users`;
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }
  async findByUsername(username: string) {
    return this.userRepository.findOne({
      where: {
        username,
      },
    });
  }
  update(id: number, updateUserDto: UpdateUserDto) {
    return `This action updates a #${id} user`;
  }

  remove(id: number) {
    return `This action removes a #${id} user`;
  }
}

第五部分:安装 JWT 依赖

安装:


pnpm add @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt

开发依赖:


pnpm add -D @types/passport-jwt @types/bcrypt

作用:

作用
@nestjs/jwtJWT生成
passport认证框架
passport-jwtJWT策略
bcrypt密码加密

第六部分:创建 Auth 模块

生成:


nest g module auth
nest g controller auth
nest g service auth

生成:


src
├─ auth
│  ├─ auth.module.ts
│  ├─ auth.controller.ts
│  └─ auth.service.ts

第七部分:配置 JWT

修改:


auth.module.ts

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [
    UsersModule,

    JwtModule.register({
      secret: 'nestjs-secret',

      signOptions: {
        expiresIn: '7d',
      },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

后面会改成


JWT_SECRET=nestjs-secret

暂时先写死。


第八部分:注册 DTO

创建:


auth/dto/login.dto.ts

import {
  IsString,
  IsNotEmpty,
} from 'class-validator';

export class LoginDto {
  @IsString()
  @IsNotEmpty()
  username: string;

  @IsString()
  @IsNotEmpty()
  password: string;
}

第九部分:实现注册

UsersService:


async create(
  username: string,
  password: string,
) {
  const user =
    this.userRepository.create({
      username,
      password,
    });

  return this.userRepository.save(user);
}

AuthService:


constructor(
  private readonly usersService: UsersService,
) {}

async register(dto: LoginDto) {
  return this.usersService.create(
    dto.username,
    dto.password,
  );
}

Controller:


@Post('register')
register(
  @Body() dto: LoginDto,
) {
  return this.authService.register(dto);
}

测试:


POST /auth/register

{
  "username":"admin",
  "password":"123456"
}

第十部分:密码加密

现在数据库:


123456

太危险。

修改:


import * as bcrypt from 'bcrypt';

注册:


const hashedPassword =
  await bcrypt.hash(
    dto.password,
    10,
  );

保存:


await this.usersService.create(
  dto.username,
  hashedPassword,
);

数据库:


$2b$10$xxxxx

而不是明文。


第十一步:实现登录

AuthService:


import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { LoginDto } from './dto/login.dto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}

  async register(dto: LoginDto) {
    const hashedPassword = await bcrypt.hash(dto.password, 10);
    return this.usersService.create(dto.username, hashedPassword);
  }
  async login(dto: LoginDto) {
    const user = await this.usersService.findByUsername(dto.username);

    if (!user) {
      throw new Error('用户不存在');
    }

    const isMatch = await bcrypt.compare(dto.password, user.password);

    if (!isMatch) {
      throw new Error('密码错误');
    }

    const payload = {
      sub: user.id,
      username: user.username,
    };

    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }
}

返回:


{
  "access_token":"xxxxx"
}

第十二部分:JWT Strategy

创建 JwtStrategy

在:

src/auth

创建:


jwt.strategy.ts

写入:

import { Injectable } from '@nestjs/common';

import { PassportStrategy } from '@nestjs/passport';

import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtPayload } from './interfaces/jwt-payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),

      ignoreExpiration: false,

      secretOrKey: 'nestjs-secret',
    });
  }

  validate(payload: JwtPayload) {
    return payload;
  }
}

src\auth\interfaces\src\auth\interfaces.ts
export interface JwtPayload {
  sub: number;

  username: string;
}

解释 Strategy

你的登录:


const payload = {
  sub:user.id,
  username:user.username,
}

生成:


{
 "access_token":"xxxxx"
}

实际上 token 里面保存:


{
 "sub":1,
 "username":"admin"
}

请求:


GET /books
Authorization: Bearer xxxxx

Passport 会:


拿token

↓

解密

↓

得到payload

↓

调用validate()

这里:


validate(payload){
 return payload;
}

返回:


{
 sub:1,
 username:'admin'
}

之后会挂到:


request.user

注册 JwtStrategy

修改:


auth.module.ts

现在:


@Module({
 imports:[
   UsersModule,
   JwtModule.register(...)
 ],

 controllers:[
   AuthController
 ],

 providers:[
   AuthService
 ]

})
export class AuthModule{}

改:


@Module({

 imports:[
   UsersModule,

   JwtModule.register({
     secret:'nestjs-secret',
     signOptions:{
       expiresIn:'7d'
     }
   })

 ],


 controllers:[
   AuthController
 ],


 providers:[

   AuthService,

   JwtStrategy

 ]

})
export class AuthModule{}

注意:

加:


JwtStrategy

测试 Strategy 是否生效

先不要 Guard。

在:


jwt.strategy.ts

里面:


async validate(payload:any){

 console.log(payload);

 return payload;

}

请求:


GET /books

现在不会打印。

因为还没有触发认证。



第十三部分:创建 Guard

创建 AuthGuard

新建:


src/auth/jwt-auth.guard.ts

内容:


import {
  Injectable
} from '@nestjs/common';

import {
  AuthGuard
} from '@nestjs/passport';



@Injectable()
export class JwtAuthGuard
extends AuthGuard('jwt') {}

这里:


AuthGuard('jwt')

对应:


PassportStrategy(
 Strategy
)

默认名字:


jwt

所以连接起来:


JwtAuthGuard

↓

passport-jwt

↓

JwtStrategy

第五步:保护 Books 接口

打开:


books.controller.ts

原来:


@Get()
findAll(){
 return this.booksService.findAll();
}

修改:


import {
 UseGuards
} from '@nestjs/common';

import {
 JwtAuthGuard
} from '../auth/jwt-auth.guard';



@UseGuards(JwtAuthGuard)
@Get()
findAll(){

 return this.booksService.findAll();

}

现在:


GET /books

直接访问:

返回:


{
 "statusCode":401,
 "message":"Unauthorized"
}

成功。

说明 Guard 生效。


第六步:获取当前登录用户

现在:


validate()

返回:


{
 sub:1,
 username:'admin'
}

怎么拿?

Controller:


@Get()
findAll(
 @Request() req
){

 console.log(req.user);


 return this.booksService.findAll();

}

请求:


Authorization:
Bearer token

输出:


{
 sub:1,
 username:'admin'
}

第七步:测试完整流程

1 注册


POST /auth/register

{
"username":"admin",
"password":"123456"
}

2 登录


POST /auth/login

返回:


{
"access_token":
"eyJhbGci..."
}

复制 token。


3 请求 books

无token:


GET /books

结果:


401

带:


Authorization: Bearer eyJhbGci...

返回:


[
 {
  "id":1,
  "name":"NestJS"
 }
]

完成。


现在你的项目结构:


src

├── auth
│
│── auth.service.ts
│
│── jwt.strategy.ts
│
│── jwt-auth.guard.ts
│
│
├── users
│
│── users.service.ts
│
│
├── books
│
│── books.controller.ts
│
│── books.service.ts

已经是标准 Nest 后端结构。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐