实现JWT之前,先简单介绍一下什么叫JWT
JWT(JSON Web Token)是一种用于“身份认证”和“信息传递”的令牌格式。
简单说:用户登录成功后,服务器会生成一个 JWT 发给前端。之后前端每次请求接口时,都把这个 JWT 带上。服务器看到 JWT 后,就知道“这个请求是谁发来的”。
JWT 的典型流程是:
用户登录,提交账号密码。服务器验证成功后,生成 JWT。前端保存 JWT,通常放在 cookie 或 localStorage 中。之后前端请求接口时,在请求头里带上
Authorization: Bearer <token>
1.安装依赖
pnpm add --save @nestjs/jwt
这个工具基于jsonwebtoken实现的,而jsonwebtoken基于javascript实现。它的维护频率并不高,且安全性上没有jose那么好,不过使用它的人依然很多。使用它的最新版也没有什么太大问题。
2.导入JwtModule
@Module({
imports: [JwtModule.register({ secret: 'hard!to-guess_secret' })],
providers: [...],
})
export class AuthModule {}
我们需要对它进行简单的配置。关于密钥的生成,要么自己写一个脚本去生成,要么找一些网站自动生成。以网站自动生成为例,
来到https://supabase.com/docs/guides/self-hosting/docker网站的Configuring legacy API keys#会自动生成JWT_SCRECT,并且刷新页面密钥也会随之刷新。
将密钥写入.env文件中, 然后写入配置。
imports: [
UserModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: {
expiresIn: '1h',
algorithm: 'HS256',
},
}),
],
这里回顾一下如何使用dotenv
安装pnpm add dotenv,然后在main.ts中导入import 'dotenv/config'那么在nestJS中的所有地方都可以使用process.env读取环境变量
3.使用
在auth.service.ts中注入
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
}
然后生成签名:
const payload = { sub: user.id, username: user.email };
return {
access_token: await this.jwtService.signAsync(payload),
};
我们可以同时修改之前的signIn和sigUp逻辑。并且由于这段生成签名的逻辑是一样的,也可以将它封装成一个函数。 完整的逻辑如下:
import {
ConflictException,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { User } from 'src/user/entities/user.entity.js';
@Injectable()
export class AuthService {
constructor(
private usersService: UserService,
private readonly jwtService: JwtService,
) {}
async signUp(
email: string,
password: string,
): Promise<{ access_token: string }> {
const user = await this.usersService.findOne(email);
if (user) {
throw new ConflictException('User already exists');
}
//hash password
const hashPassword = await bcrypt.hash(password, 10);
const createdUser = await this.usersService.create({
email,
password: hashPassword,
});
return this.generateAccessToken(createdUser);
}
async signIn(email: string, pass: string): Promise<{ access_token: string }> {
const user = await this.usersService.findOne(email);
if (!user) {
throw new NotFoundException('User not found');
}
const isPasswordValid = await bcrypt.compare(pass, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials');
}
return this.generateAccessToken(user);
}
private async generateAccessToken(user: User) {
const payload = { sub: user.id, username: user.email };
return {
access_token: await this.jwtService.signAsync(payload),
};
}
}