为什么要使用Jwt?
总结:获取用户信息不需要每次查询数据!后端可以直接拿密钥解密加密的用户信息。当然这是我的理解,至于官方的语言,请移步GPT
使用
这里我不再赘述项目的创建,环境的配置,假设环境已经配置好,有一个Auth模块
目录结构如下
.
└──src
├── auth
│ ├── auth.controller.ts
│ ├── auth.guard.ts
│ ├── auth.module.ts
│ ├── auth.service.ts
│ ├── decorators
│ │ └── public.decorator.ts
│ ├── dto
│ │ └── login-auth.dto.ts
│ ├── entities
│ │ └── auth.entity.ts
│ └── strategies
│ └── jwt.strategy.ts
按照写代码的逻辑,我将从controller层开始,我演示两个接口,一个是login这样的公共接口
公共接口编写(Public装饰器)
// auth.controller.ts
import {Get, Post, Body} from '@nestjs/common';
import {Public} from './decorators/public.decorator';
import {ApiBody, ApiOperation, ApiQuery} from '@nestjs/swagger';
import {LoginAuthDto} from './dto/login-auth.dto';
import {JwtService} from '@nestjs/jwt';
...
@Post('login') // 请求的接口路径
@ApiOperation({summary: "app登录", description: "user login"}) // swagger文档配置
@ApiBody({type: LoginAuthDto}) // 请求携带数据类型
@Public() // 这里是自定义的装饰器,我们需要自己配置。用这个装饰器表示不需要鉴权就可以访问
async login(@Body() req: LoginAuthDto): Promise<any> {
// 调用service层的逻辑,里面应该是逻辑处理,这里的authServic.login方法是返回数据库的数据
const {id, userName, openid, phone} = await this.authService.login(req);
// 这里也可以简写,我这里为了方便阅读,展开来了
const payload = {
username: userName,
id: id,
openid: openid,
phone: phone,
}
// 返回客户端的数据,access_token
return {
access_token: this.jwtService.sign(payload, {secret: process.env.JWT_ACCESS_SECRET})
}
}
...
...
// 使用鉴权模块
@UseGuards(JwtAuthGuard)
@Get('protocol')
@ApiOperation({summary: "协议", description: "protocol"})
findProtocol(@Req() req) {
// 使用req.user获取用户信息!
console.log('protocol controller')
console.log(req.user)
console.log('-----------------')
return this.initService.findProtocol();
}
...
自定义Public装饰器
// public.decorator.ts
// 设置自定义装饰器
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
有了Public装饰器,我们编写拦截验证的代码,我这里没有提到需要安装的库,如果报红直接npm i,这就是我的最佳实践
// auth.guard.ts
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import {Reflector} from '@nestjs/core';
import {IS_PUBLIC_KEY} from './decorators/public.decorator';
import {AuthGuard} from "@nestjs/passport";
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
// 在这里检查路由是否有 `@Public()` 装饰器
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
// 如果路由是公共的,则跳过 JWT 验证
if (isPublic) {
return true;
}
// 否则,继续执行 JWT 验证
return super.canActivate(context);
}
handleRequest(err, user, info, context: ExecutionContext) {
console.log('handleRequest')
console.log(user)
// 如果用户不存在且路由不是公共的,抛出异常
if (err || (!user && !this.isPublic(context))) {
throw err || new UnauthorizedException();
}
return user;
}
private isPublic(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
return isPublic;
}
}
// strategies --> jwt.strategy.ts
import { ConfigService } from '@nestjs/config';
import { UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { StrategyOptions, Strategy, ExtractJwt } from 'passport-jwt';
import {InjectRepository} from "@nestjs/typeorm";
import {User} from "../../user/entities/user.entity";
import {Repository} from "typeorm";
import {UserService} from "../../user/user.service";
export class JwtStorage extends PassportStrategy(Strategy, 'jwt') {
// 实例化对象
constructor(
// 查询数据库的基本操作
@InjectRepository(User)
private readonly userRepository: Repository<User>,
// 访问配置文件,如生成jwt的密钥
private readonly configService: ConfigService,
// Service层实例化
private readonly userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get('JWT_ACCESS_SECRET'),
} as StrategyOptions);
}
async validate(user: User) {
// 查询数据库操作,如需要可以自行编写
const existUser = await this.userService.findBy({phone: user.phone});
if (!existUser) {
throw new UnauthorizedException('token不正确');
}
return existUser;
}
}
最后,在模块代码里面注册代码
// auth.module.ts
import {Module} from '@nestjs/common';
import {AuthService} from './auth.service';
import {AuthController} from './auth.controller';
import {UserModule} from 'src/user/user.module';
import {ConfigModule, ConfigService} from '@nestjs/config';
import {JwtModule} from '@nestjs/jwt';
import {RedisModule} from 'src/redis/redis.module';
import {PassportModule} from '@nestjs/passport';
import {JwtAuthGuard} from './auth.guard';
import {JwtStorage} from "./strategies/jwt.strategy";
@Module({
imports: [
// 不需要可以直接省略
UserModule,
RedisModule,
PassportModule,
// 重点是这一块,需要获取环境配置的密钥
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('JWT_ACCESS_SECRET'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRATION_TIME'),
}
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [
// 重点
JwtAuthGuard,
AuthService,
JwtStorage
],
exports: [
// 重点
PassportModule,
JwtModule,
JwtAuthGuard
]
})
export class AuthModule {
}
最后app.module.ts把auth模块注册就好了!
请求成功
不携带Authorization请求