用 NestJS 从零开发一个完整的小项目:图书管理系统(第五阶段:用户模块 + JWT 登录认证)
·
第五阶段:用户模块 + 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/jwt | JWT生成 |
| passport | 认证框架 |
| passport-jwt | JWT策略 |
| 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 后端结构。
更多推荐



所有评论(0)